summaryrefslogtreecommitdiff
path: root/tests/regressiontests/forms
diff options
context:
space:
mode:
Diffstat (limited to 'tests/regressiontests/forms')
-rw-r--r--tests/regressiontests/forms/error_messages.py2
-rw-r--r--tests/regressiontests/forms/extra.py8
-rw-r--r--tests/regressiontests/forms/fields.py75
-rw-r--r--tests/regressiontests/forms/forms.py74
-rw-r--r--tests/regressiontests/forms/formsets.py575
-rw-r--r--tests/regressiontests/forms/media.py359
-rw-r--r--tests/regressiontests/forms/models.py2
-rw-r--r--tests/regressiontests/forms/regressions.py2
-rw-r--r--tests/regressiontests/forms/tests.py4
-rw-r--r--tests/regressiontests/forms/util.py6
-rw-r--r--tests/regressiontests/forms/widgets.py162
11 files changed, 1239 insertions, 30 deletions
diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
index 580326f894..ec91b57a06 100644
--- a/tests/regressiontests/forms/error_messages.py
+++ b/tests/regressiontests/forms/error_messages.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
tests = r"""
->>> from django.newforms import *
+>>> from django.forms import *
>>> from django.core.files.uploadedfile import SimpleUploadedFile
# CharField ###################################################################
diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py
index a8b369715d..80f7ef6535 100644
--- a/tests/regressiontests/forms/extra.py
+++ b/tests/regressiontests/forms/extra.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
tests = r"""
->>> from django.newforms import *
+>>> from django.forms import *
>>> from django.utils.encoding import force_unicode
>>> import datetime
>>> import time
@@ -14,12 +14,12 @@ tests = r"""
# Extra stuff #
###############
-The newforms library comes with some extra, higher-level Field and Widget
+The forms library comes with some extra, higher-level Field and Widget
classes that demonstrate some of the library's abilities.
# SelectDateWidget ############################################################
->>> from django.newforms.extras import SelectDateWidget
+>>> from django.forms.extras import SelectDateWidget
>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'))
>>> print w.render('mydate', '')
<select name="mydate_month" id="id_mydate_month">
@@ -424,7 +424,7 @@ u'sirrobin'
# Test overriding ErrorList in a form #
#######################################
->>> from django.newforms.util import ErrorList
+>>> from django.forms.util import ErrorList
>>> class DivErrorList(ErrorList):
... def __unicode__(self):
... return self.as_divs()
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
index f266e7bc50..c70ff2dff3 100644
--- a/tests/regressiontests/forms/fields.py
+++ b/tests/regressiontests/forms/fields.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
tests = r"""
->>> from django.newforms import *
->>> from django.newforms.widgets import RadioFieldRenderer
+>>> from django.forms import *
+>>> from django.forms.widgets import RadioFieldRenderer
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> import datetime
>>> import time
@@ -17,7 +17,7 @@ tests = r"""
##########
Each Field class does some sort of validation. Each Field has a clean() method,
-which either raises django.newforms.ValidationError or returns the "clean"
+which either raises django.forms.ValidationError or returns the "clean"
data -- usually a Unicode object, but, in some rare cases, a list.
Each Field's __init__() takes at least these parameters:
@@ -980,7 +980,7 @@ False
# ChoiceField #################################################################
->>> f = ChoiceField(choices=[('1', '1'), ('2', '2')])
+>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
>>> f.clean('')
Traceback (most recent call last):
...
@@ -996,9 +996,9 @@ u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
+ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
->>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
+>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
>>> f.clean('')
u''
>>> f.clean(None)
@@ -1010,7 +1010,7 @@ u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
+ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
>>> f.clean('J')
@@ -1018,7 +1018,25 @@ u'J'
>>> f.clean('John')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
+ValidationError: [u'Select a valid choice. John is not one of the available choices.']
+
+>>> f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
+>>> f.clean(1)
+u'1'
+>>> f.clean('1')
+u'1'
+>>> f.clean(3)
+u'3'
+>>> f.clean('3')
+u'3'
+>>> f.clean(5)
+u'5'
+>>> f.clean('5')
+u'5'
+>>> f.clean('6')
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
# NullBooleanField ############################################################
@@ -1036,7 +1054,7 @@ False
# MultipleChoiceField #########################################################
->>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')])
+>>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
>>> f.clean('')
Traceback (most recent call last):
...
@@ -1072,7 +1090,7 @@ Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
->>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
+>>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
>>> f.clean('')
[]
>>> f.clean(None)
@@ -1100,6 +1118,29 @@ Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
+>>> f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
+>>> f.clean([1])
+[u'1']
+>>> f.clean(['1'])
+[u'1']
+>>> f.clean([1, 5])
+[u'1', u'5']
+>>> f.clean([1, '5'])
+[u'1', u'5']
+>>> f.clean(['1', 5])
+[u'1', u'5']
+>>> f.clean(['1', '5'])
+[u'1', u'5']
+>>> f.clean(['6'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
+>>> f.clean(['1','6'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
+
+
# ComboField ##################################################################
ComboField takes a list of fields that should be used to validate a value,
@@ -1153,29 +1194,29 @@ u''
... return x
...
>>> import os
->>> from django import newforms as forms
+>>> from django import forms
>>> path = forms.__file__
>>> path = os.path.dirname(path) + '/'
>>> fix_os_paths(path)
-'.../django/newforms/'
+'.../django/forms/'
>>> f = forms.FilePathField(path=path)
>>> f.choices.sort()
>>> fix_os_paths(f.choices)
-[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/__init__.pyc', '__init__.pyc'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/fields.pyc', 'fields.pyc'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/forms.pyc', 'forms.pyc'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/models.pyc', 'models.pyc'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/util.pyc', 'util.pyc'), ('.../django/newforms/widgets.py', 'widgets.py'), ('.../django/newforms/widgets.pyc', 'widgets.pyc')]
+[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/__init__.pyc', '__init__.pyc'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/fields.pyc', 'fields.pyc'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/forms.pyc', 'forms.pyc'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/models.pyc', 'models.pyc'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/util.pyc', 'util.pyc'), ('.../django/forms/widgets.py', 'widgets.py'), ('.../django/forms/widgets.pyc', 'widgets.pyc')]
>>> f.clean('fields.py')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
+ValidationError: [u'Select a valid choice. fields.py is not one of the available choices.']
>>> fix_os_paths(f.clean(path + 'fields.py'))
-u'.../django/newforms/fields.py'
+u'.../django/forms/fields.py'
>>> f = forms.FilePathField(path=path, match='^.*?\.py$')
>>> f.choices.sort()
>>> fix_os_paths(f.choices)
-[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')]
+[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')]
>>> f = forms.FilePathField(path=path, recursive=True, match='^.*?\.py$')
>>> f.choices.sort()
>>> fix_os_paths(f.choices)
-[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/extras/__init__.py', 'extras/__init__.py'), ('.../django/newforms/extras/widgets.py', 'extras/widgets.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')]
+[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/extras/__init__.py', 'extras/__init__.py'), ('.../django/forms/extras/widgets.py', 'extras/widgets.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')]
# SplitDateTimeField ##########################################################
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
index 041fa4054c..6e6e4f79bf 100644
--- a/tests/regressiontests/forms/forms.py
+++ b/tests/regressiontests/forms/forms.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
tests = r"""
->>> from django.newforms import *
+>>> from django.forms import *
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> import datetime
>>> import time
@@ -1667,4 +1667,76 @@ the list of errors is empty). You can also use it in {% if %} statements.
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
<input type="submit" />
</form>
+
+
+# The empty_permitted attribute ##############################################
+
+Sometimes (pretty much in formsets) we want to allow a form to pass validation
+if it is completely empty. We can accomplish this by using the empty_permitted
+agrument to a form constructor.
+
+>>> class SongForm(Form):
+... artist = CharField()
+... name = CharField()
+
+First let's show what happens id empty_permitted=False (the default):
+
+>>> data = {'artist': '', 'song': ''}
+
+>>> form = SongForm(data, empty_permitted=False)
+>>> form.is_valid()
+False
+>>> form.errors
+{'name': [u'This field is required.'], 'artist': [u'This field is required.']}
+>>> form.cleaned_data
+Traceback (most recent call last):
+...
+AttributeError: 'SongForm' object has no attribute 'cleaned_data'
+
+
+Now let's show what happens when empty_permitted=True and the form is empty.
+
+>>> form = SongForm(data, empty_permitted=True)
+>>> form.is_valid()
+True
+>>> form.errors
+{}
+>>> form.cleaned_data
+{}
+
+But if we fill in data for one of the fields, the form is no longer empty and
+the whole thing must pass validation.
+
+>>> data = {'artist': 'The Doors', 'song': ''}
+>>> form = SongForm(data, empty_permitted=False)
+>>> form.is_valid()
+False
+>>> form.errors
+{'name': [u'This field is required.']}
+>>> form.cleaned_data
+Traceback (most recent call last):
+...
+AttributeError: 'SongForm' object has no attribute 'cleaned_data'
+
+If a field is not given in the data then None is returned for its data. Lets
+make sure that when checking for empty_permitted that None is treated
+accordingly.
+
+>>> data = {'artist': None, 'song': ''}
+>>> form = SongForm(data, empty_permitted=True)
+>>> form.is_valid()
+True
+
+However, we *really* need to be sure we are checking for None as any data in
+initial that returns False on a boolean call needs to be treated literally.
+
+>>> class PriceForm(Form):
+... amount = FloatField()
+... qty = IntegerField()
+
+>>> data = {'amount': '0.0', 'qty': ''}
+>>> form = PriceForm(data, initial={'amount': 0.0}, empty_permitted=True)
+>>> form.is_valid()
+True
+
"""
diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py
new file mode 100644
index 0000000000..bbbd4cee5a
--- /dev/null
+++ b/tests/regressiontests/forms/formsets.py
@@ -0,0 +1,575 @@
+# -*- coding: utf-8 -*-
+tests = """
+# Basic FormSet creation and usage ############################################
+
+FormSet allows us to use multiple instance of the same form on 1 page. For now,
+the best way to create a FormSet is by using the formset_factory function.
+
+>>> from django.forms import Form, CharField, IntegerField, ValidationError
+>>> from django.forms.formsets import formset_factory, BaseFormSet
+
+>>> class Choice(Form):
+... choice = CharField()
+... votes = IntegerField()
+
+>>> ChoiceFormSet = formset_factory(Choice)
+
+A FormSet constructor takes the same arguments as Form. Let's create a FormSet
+for adding data. By default, it displays 1 blank form. It can display more,
+but we'll look at how to do so later.
+
+>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
+>>> print formset
+<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_FORMS" value="0" />
+<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
+<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
+
+
+On thing to note is that there needs to be a special value in the data. This
+value tells the FormSet how many forms were displayed so it can tell how
+many forms it needs to clean and validate. You could use javascript to create
+new forms on the client side, but they won't get validated unless you increment
+the TOTAL_FORMS field appropriately.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... }
+
+We treat FormSet pretty much like we would treat a normal Form. FormSet has an
+is_valid method, and a cleaned_data or errors attribute depending on whether all
+the forms passed validation. However, unlike a Form instance, cleaned_data and
+errors will be a list of dicts rather than just a single dict.
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> [form.cleaned_data for form in formset.forms]
+[{'votes': 100, 'choice': u'Calexico'}]
+
+If a FormSet was not passed any data, its is_valid method should return False.
+>>> formset = ChoiceFormSet()
+>>> formset.is_valid()
+False
+
+FormSet instances can also have an error attribute if validation failed for
+any of the forms.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+False
+>>> formset.errors
+[{'votes': [u'This field is required.']}]
+
+
+We can also prefill a FormSet with existing data by providing an ``initial``
+argument to the constructor. ``initial`` should be a list of dicts. By default,
+an extra blank form is included.
+
+>>> initial = [{'choice': u'Calexico', 'votes': 100}]
+>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> for form in formset.forms:
+... print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" /></li>
+
+
+Let's simulate what would happen if we submitted this form.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-1-choice': '',
+... 'choices-1-votes': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> [form.cleaned_data for form in formset.forms]
+[{'votes': 100, 'choice': u'Calexico'}, {}]
+
+But the second form was blank! Shouldn't we get some errors? No. If we display
+a form as blank, it's ok for it to be submitted as blank. If we fill out even
+one of the fields of a blank form though, it will be validated. We may want to
+required that at least x number of forms are completed, but we'll show how to
+handle that later.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-1-choice': 'The Decemberists',
+... 'choices-1-votes': '', # missing value
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+False
+>>> formset.errors
+[{}, {'votes': [u'This field is required.']}]
+
+If we delete data that was pre-filled, we should get an error. Simply removing
+data from form fields isn't the proper way to delete it. We'll see how to
+handle that case later.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': '', # deleted value
+... 'choices-0-votes': '', # deleted value
+... 'choices-1-choice': '',
+... 'choices-1-votes': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+False
+>>> formset.errors
+[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}]
+
+
+# Displaying more than 1 blank form ###########################################
+
+We can also display more than 1 empty form at a time. To do so, pass a
+extra argument to formset_factory.
+
+>>> ChoiceFormSet = formset_factory(Choice, extra=3)
+
+>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
+>>> for form in formset.forms:
+... print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+
+Since we displayed every form as blank, we will also accept them back as blank.
+This may seem a little strange, but later we will show how to require a minimum
+number of forms to be completed.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': '',
+... 'choices-0-votes': '',
+... 'choices-1-choice': '',
+... 'choices-1-votes': '',
+... 'choices-2-choice': '',
+... 'choices-2-votes': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> [form.cleaned_data for form in formset.forms]
+[{}, {}, {}]
+
+
+We can just fill out one of the forms.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-1-choice': '',
+... 'choices-1-votes': '',
+... 'choices-2-choice': '',
+... 'choices-2-votes': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> [form.cleaned_data for form in formset.forms]
+[{'votes': 100, 'choice': u'Calexico'}, {}, {}]
+
+
+And once again, if we try to partially complete a form, validation will fail.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-1-choice': 'The Decemberists',
+... 'choices-1-votes': '', # missing value
+... 'choices-2-choice': '',
+... 'choices-2-votes': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+False
+>>> formset.errors
+[{}, {'votes': [u'This field is required.']}, {}]
+
+
+The extra argument also works when the formset is pre-filled with initial
+data.
+
+>>> initial = [{'choice': u'Calexico', 'votes': 100}]
+>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> for form in formset.forms:
+... print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+<li>Choice: <input type="text" name="choices-3-choice" /></li>
+<li>Votes: <input type="text" name="choices-3-votes" /></li>
+
+
+# FormSets with deletion ######################################################
+
+We can easily add deletion ability to a FormSet with an agrument to
+formset_factory. This will add a boolean field to each form instance. When
+that boolean field is True, the form will be in formset.deleted_forms
+
+>>> ChoiceFormSet = formset_factory(Choice, can_delete=True)
+
+>>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
+>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> for form in formset.forms:
+... print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
+<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
+
+To delete something, we just need to set that form's special delete field to
+'on'. Let's go ahead and delete Fergie.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-0-DELETE': '',
+... 'choices-1-choice': 'Fergie',
+... 'choices-1-votes': '900',
+... 'choices-1-DELETE': 'on',
+... 'choices-2-choice': '',
+... 'choices-2-votes': '',
+... 'choices-2-DELETE': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> [form.cleaned_data for form in formset.forms]
+[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}]
+>>> [form.cleaned_data for form in formset.deleted_forms]
+[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}]
+
+
+# FormSets with ordering ######################################################
+
+We can also add ordering ability to a FormSet with an agrument to
+formset_factory. This will add a integer field to each form instance. When
+form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
+order specified by the ordering fields. If a number is duplicated in the set
+of ordering fields, for instance form 0 and form 3 are both marked as 1, then
+the form index used as a secondary ordering criteria. In order to put
+something at the front of the list, you'd need to set it's order to 0.
+
+>>> ChoiceFormSet = formset_factory(Choice, can_order=True)
+
+>>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
+>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> for form in formset.forms:
+... print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
+<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+<li>Order: <input type="text" name="choices-2-ORDER" /></li>
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-0-ORDER': '1',
+... 'choices-1-choice': 'Fergie',
+... 'choices-1-votes': '900',
+... 'choices-1-ORDER': '2',
+... 'choices-2-choice': 'The Decemberists',
+... 'choices-2-votes': '500',
+... 'choices-2-ORDER': '0',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> for form in formset.ordered_forms:
+... print form.cleaned_data
+{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'}
+{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
+{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
+
+Ordering fields are allowed to be left blank, and if they *are* left blank,
+they will be sorted below everything else.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-0-ORDER': '1',
+... 'choices-1-choice': 'Fergie',
+... 'choices-1-votes': '900',
+... 'choices-1-ORDER': '2',
+... 'choices-2-choice': 'The Decemberists',
+... 'choices-2-votes': '500',
+... 'choices-2-ORDER': '',
+... 'choices-3-choice': 'Basia Bulat',
+... 'choices-3-votes': '50',
+... 'choices-3-ORDER': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> for form in formset.ordered_forms:
+... print form.cleaned_data
+{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'}
+{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'}
+{'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'}
+{'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'}
+
+
+# FormSets with ordering + deletion ###########################################
+
+Let's try throwing ordering and deletion into the same form.
+
+>>> ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True)
+
+>>> initial = [
+... {'choice': u'Calexico', 'votes': 100},
+... {'choice': u'Fergie', 'votes': 900},
+... {'choice': u'The Decemberists', 'votes': 500},
+... ]
+>>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+>>> for form in formset.forms:
+... print form.as_ul()
+<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
+<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
+<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
+<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
+<li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
+<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-3-choice" /></li>
+<li>Votes: <input type="text" name="choices-3-votes" /></li>
+<li>Order: <input type="text" name="choices-3-ORDER" /></li>
+<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>
+
+Let's delete Fergie, and put The Decemberists ahead of Calexico.
+
+>>> data = {
+... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
+... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
+... 'choices-MAX_FORMS': '0', # the max number of forms
+... 'choices-0-choice': 'Calexico',
+... 'choices-0-votes': '100',
+... 'choices-0-ORDER': '1',
+... 'choices-0-DELETE': '',
+... 'choices-1-choice': 'Fergie',
+... 'choices-1-votes': '900',
+... 'choices-1-ORDER': '2',
+... 'choices-1-DELETE': 'on',
+... 'choices-2-choice': 'The Decemberists',
+... 'choices-2-votes': '500',
+... 'choices-2-ORDER': '0',
+... 'choices-2-DELETE': '',
+... 'choices-3-choice': '',
+... 'choices-3-votes': '',
+... 'choices-3-ORDER': '',
+... 'choices-3-DELETE': '',
+... }
+
+>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+>>> formset.is_valid()
+True
+>>> for form in formset.ordered_forms:
+... print form.cleaned_data
+{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'}
+{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'}
+>>> [form.cleaned_data for form in formset.deleted_forms]
+[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}]
+
+
+# FormSet clean hook ##########################################################
+
+FormSets have a hook for doing extra validation that shouldn't be tied to any
+particular form. It follows the same pattern as the clean hook on Forms.
+
+Let's define a FormSet that takes a list of favorite drinks, but raises am
+error if there are any duplicates.
+
+>>> class FavoriteDrinkForm(Form):
+... name = CharField()
+...
+
+>>> class BaseFavoriteDrinksFormSet(BaseFormSet):
+... def clean(self):
+... seen_drinks = []
+... for drink in self.cleaned_data:
+... if drink['name'] in seen_drinks:
+... raise ValidationError('You may only specify a drink once.')
+... seen_drinks.append(drink['name'])
+...
+
+>>> FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
+... formset=BaseFavoriteDrinksFormSet, extra=3)
+
+We start out with a some duplicate data.
+
+>>> data = {
+... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
+... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'drinks-MAX_FORMS': '0', # the max number of forms
+... 'drinks-0-name': 'Gin and Tonic',
+... 'drinks-1-name': 'Gin and Tonic',
+... }
+
+>>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
+>>> formset.is_valid()
+False
+
+Any errors raised by formset.clean() are available via the
+formset.non_form_errors() method.
+
+>>> for error in formset.non_form_errors():
+... print error
+You may only specify a drink once.
+
+
+Make sure we didn't break the valid case.
+
+>>> data = {
+... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
+... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+... 'drinks-MAX_FORMS': '0', # the max number of forms
+... 'drinks-0-name': 'Gin and Tonic',
+... 'drinks-1-name': 'Bloody Mary',
+... }
+
+>>> formset = FavoriteDrinksFormSet(data, prefix='drinks')
+>>> formset.is_valid()
+True
+>>> for error in formset.non_form_errors():
+... print error
+
+# Limiting the maximum number of forms ########################################
+
+# Base case for max_num.
+
+>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
+>>> formset = LimitedFavoriteDrinkFormSet()
+>>> for form in formset.forms:
+... print form
+<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
+
+# Ensure the that max_num has no affect when extra is less than max_forms.
+
+>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
+>>> formset = LimitedFavoriteDrinkFormSet()
+>>> for form in formset.forms:
+... print form
+<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
+
+# max_num with initial data
+
+# More initial forms than max_num will result in only the first max_num of
+# them to be displayed with no extra forms.
+
+>>> initial = [
+... {'name': 'Gin Tonic'},
+... {'name': 'Bloody Mary'},
+... {'name': 'Jack and Coke'},
+... ]
+>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
+>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
+>>> for form in formset.forms:
+... print form
+<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" value="Bloody Mary" id="id_form-1-name" /></td></tr>
+
+# One form from initial and extra=3 with max_num=2 should result in the one
+# initial form and one extra.
+
+>>> initial = [
+... {'name': 'Gin Tonic'},
+... ]
+>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
+>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
+>>> for form in formset.forms:
+... print form
+<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
+
+
+# Regression test for #6926 ##################################################
+
+Make sure the management form has the correct prefix.
+
+>>> formset = FavoriteDrinksFormSet()
+>>> formset.management_form.prefix
+'form'
+
+>>> formset = FavoriteDrinksFormSet(data={})
+>>> formset.management_form.prefix
+'form'
+
+>>> formset = FavoriteDrinksFormSet(initial={})
+>>> formset.management_form.prefix
+'form'
+
+"""
diff --git a/tests/regressiontests/forms/media.py b/tests/regressiontests/forms/media.py
new file mode 100644
index 0000000000..d05db1f164
--- /dev/null
+++ b/tests/regressiontests/forms/media.py
@@ -0,0 +1,359 @@
+# -*- coding: utf-8 -*-
+# Tests for the media handling on widgets and forms
+
+media_tests = r"""
+>>> from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget
+>>> from django.conf import settings
+>>> ORIGINAL_MEDIA_URL = settings.MEDIA_URL
+>>> settings.MEDIA_URL = 'http://media.example.com/media/'
+
+# Check construction of media objects
+>>> m = Media(css={'all': ('path/to/css1','/path/to/css2')}, js=('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3'))
+>>> print m
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+>>> class Foo:
+... css = {
+... 'all': ('path/to/css1','/path/to/css2')
+... }
+... js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+>>> m3 = Media(Foo)
+>>> print m3
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+>>> m3 = Media(Foo)
+>>> print m3
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# A widget can exist without a media definition
+>>> class MyWidget(TextInput):
+... pass
+
+>>> w = MyWidget()
+>>> print w.media
+<BLANKLINE>
+
+###############################################################
+# DSL Class-based media definitions
+###############################################################
+
+# A widget can define media if it needs to.
+# Any absolute path will be preserved; relative paths are combined
+# with the value of settings.MEDIA_URL
+>>> class MyWidget1(TextInput):
+... class Media:
+... css = {
+... 'all': ('path/to/css1','/path/to/css2')
+... }
+... js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+>>> w1 = MyWidget1()
+>>> print w1.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# Media objects can be interrogated by media type
+>>> print w1.media['css']
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+
+>>> print w1.media['js']
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# Media objects can be combined. Any given media resource will appear only
+# once. Duplicated media definitions are ignored.
+>>> class MyWidget2(TextInput):
+... class Media:
+... css = {
+... 'all': ('/path/to/css2','/path/to/css3')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> class MyWidget3(TextInput):
+... class Media:
+... css = {
+... 'all': ('/path/to/css3','path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w2 = MyWidget2()
+>>> w3 = MyWidget3()
+>>> print w1.media + w2.media + w3.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+# Check that media addition hasn't affected the original objects
+>>> print w1.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+###############################################################
+# Property-based media definitions
+###############################################################
+
+# Widget media can be defined as a property
+>>> class MyWidget4(TextInput):
+... def _media(self):
+... return Media(css={'all': ('/some/path',)}, js = ('/some/js',))
+... media = property(_media)
+
+>>> w4 = MyWidget4()
+>>> print w4.media
+<link href="/some/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/some/js"></script>
+
+# Media properties can reference the media of their parents
+>>> class MyWidget5(MyWidget4):
+... def _media(self):
+... return super(MyWidget5, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
+... media = property(_media)
+
+>>> w5 = MyWidget5()
+>>> print w5.media
+<link href="/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/some/js"></script>
+<script type="text/javascript" src="/other/js"></script>
+
+# Media properties can reference the media of their parents,
+# even if the parent media was defined using a class
+>>> class MyWidget6(MyWidget1):
+... def _media(self):
+... return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
+... media = property(_media)
+
+>>> w6 = MyWidget6()
+>>> print w6.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/other/js"></script>
+
+###############################################################
+# Inheritance of media
+###############################################################
+
+# If a widget extends another but provides no media definition, it inherits the parent widget's media
+>>> class MyWidget7(MyWidget1):
+... pass
+
+>>> w7 = MyWidget7()
+>>> print w7.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# If a widget extends another but defines media, it extends the parent widget's media by default
+>>> class MyWidget8(MyWidget1):
+... class Media:
+... css = {
+... 'all': ('/path/to/css3','path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w8 = MyWidget8()
+>>> print w8.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+# If a widget extends another but defines media, it extends the parents widget's media,
+# even if the parent defined media using a property.
+>>> class MyWidget9(MyWidget4):
+... class Media:
+... css = {
+... 'all': ('/other/path',)
+... }
+... js = ('/other/js',)
+
+>>> w9 = MyWidget9()
+>>> print w9.media
+<link href="/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/some/js"></script>
+<script type="text/javascript" src="/other/js"></script>
+
+# A widget can disable media inheritance by specifying 'extend=False'
+>>> class MyWidget10(MyWidget1):
+... class Media:
+... extend = False
+... css = {
+... 'all': ('/path/to/css3','path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w10 = MyWidget10()
+>>> print w10.media
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+# A widget can explicitly enable full media inheritance by specifying 'extend=True'
+>>> class MyWidget11(MyWidget1):
+... class Media:
+... extend = True
+... css = {
+... 'all': ('/path/to/css3','path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w11 = MyWidget11()
+>>> print w11.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+# A widget can enable inheritance of one media type by specifying extend as a tuple
+>>> class MyWidget12(MyWidget1):
+... class Media:
+... extend = ('css',)
+... css = {
+... 'all': ('/path/to/css3','path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w12 = MyWidget12()
+>>> print w12.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+###############################################################
+# Multi-media handling for CSS
+###############################################################
+
+# A widget can define CSS media for multiple output media types
+>>> class MultimediaWidget(TextInput):
+... class Media:
+... css = {
+... 'screen, print': ('/file1','/file2'),
+... 'screen': ('/file3',),
+... 'print': ('/file4',)
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> multimedia = MultimediaWidget()
+>>> print multimedia.media
+<link href="/file4" type="text/css" media="print" rel="stylesheet" />
+<link href="/file3" type="text/css" media="screen" rel="stylesheet" />
+<link href="/file1" type="text/css" media="screen, print" rel="stylesheet" />
+<link href="/file2" type="text/css" media="screen, print" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+###############################################################
+# Multiwidget media handling
+###############################################################
+
+# MultiWidgets have a default media definition that gets all the
+# media from the component widgets
+>>> class MyMultiWidget(MultiWidget):
+... def __init__(self, attrs=None):
+... widgets = [MyWidget1, MyWidget2, MyWidget3]
+... super(MyMultiWidget, self).__init__(widgets, attrs)
+
+>>> mymulti = MyMultiWidget()
+>>> print mymulti.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+###############################################################
+# Media processing for forms
+###############################################################
+
+# You can ask a form for the media required by its widgets.
+>>> class MyForm(Form):
+... field1 = CharField(max_length=20, widget=MyWidget1())
+... field2 = CharField(max_length=20, widget=MyWidget2())
+>>> f1 = MyForm()
+>>> print f1.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+# Form media can be combined to produce a single media definition.
+>>> class AnotherForm(Form):
+... field3 = CharField(max_length=20, widget=MyWidget3())
+>>> f2 = AnotherForm()
+>>> print f1.media + f2.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+
+# Forms can also define media, following the same rules as widgets.
+>>> class FormWithMedia(Form):
+... field1 = CharField(max_length=20, widget=MyWidget1())
+... field2 = CharField(max_length=20, widget=MyWidget2())
+... class Media:
+... js = ('/some/form/javascript',)
+... css = {
+... 'all': ('/some/form/css',)
+... }
+>>> f3 = FormWithMedia()
+>>> print f3.media
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+<script type="text/javascript" src="/some/form/javascript"></script>
+
+>>> settings.MEDIA_URL = ORIGINAL_MEDIA_URL
+""" \ No newline at end of file
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
index c7ce128560..98b9233d80 100644
--- a/tests/regressiontests/forms/models.py
+++ b/tests/regressiontests/forms/models.py
@@ -15,7 +15,7 @@ class ChoiceModel(models.Model):
name = models.CharField(max_length=10)
__test__ = {'API_TESTS': """
->>> from django.newforms import form_for_model, form_for_instance
+>>> from django.forms import form_for_model, form_for_instance
# Boundary conditions on a PostitiveIntegerField #########################
>>> BoundaryForm = form_for_model(BoundaryModel)
diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py
index cbc8095e60..87390d3cd1 100644
--- a/tests/regressiontests/forms/regressions.py
+++ b/tests/regressiontests/forms/regressions.py
@@ -3,7 +3,7 @@
tests = r"""
It should be possible to re-use attribute dictionaries (#3810)
->>> from django.newforms import *
+>>> from django.forms import *
>>> extra_attrs = {'class': 'special'}
>>> class TestForm(Form):
... f1 = CharField(max_length=10, widget=TextInput(attrs=extra_attrs))
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index bb0e30b874..ff8213c8d9 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -26,6 +26,8 @@ from localflavor.za import tests as localflavor_za_tests
from regressions import tests as regression_tests
from util import tests as util_tests
from widgets import tests as widgets_tests
+from formsets import tests as formset_tests
+from media import media_tests
__test__ = {
'extra_tests': extra_tests,
@@ -53,6 +55,8 @@ __test__ = {
'localflavor_us_tests': localflavor_us_tests,
'localflavor_za_tests': localflavor_za_tests,
'regression_tests': regression_tests,
+ 'formset_tests': formset_tests,
+ 'media_tests': media_tests,
'util_tests': util_tests,
'widgets_tests': widgets_tests,
}
diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
index bfaf73f6bc..68c082c114 100644
--- a/tests/regressiontests/forms/util.py
+++ b/tests/regressiontests/forms/util.py
@@ -1,17 +1,17 @@
# coding: utf-8
"""
-Tests for newforms/util.py module.
+Tests for forms/util.py module.
"""
tests = r"""
->>> from django.newforms.util import *
+>>> from django.forms.util import *
>>> from django.utils.translation import ugettext_lazy
###########
# flatatt #
###########
->>> from django.newforms.util import flatatt
+>>> from django.forms.util import flatatt
>>> flatatt({'id': "header"})
u' id="header"'
>>> flatatt({'class': "news", 'title': "Read this"})
diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py
index 2c6b51a8ec..40c4d01793 100644
--- a/tests/regressiontests/forms/widgets.py
+++ b/tests/regressiontests/forms/widgets.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
tests = r"""
->>> from django.newforms import *
->>> from django.newforms.widgets import RadioFieldRenderer
+>>> from django.forms import *
+>>> from django.forms.widgets import RadioFieldRenderer
>>> from django.utils.safestring import mark_safe
>>> import datetime
>>> import time
@@ -202,6 +202,30 @@ u'<input type="file" class="fun" name="email" />'
>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
u'<input type="file" class="fun" name="email" />'
+Test for the behavior of _has_changed for FileInput. The value of data will
+more than likely come from request.FILES. The value of initial data will
+likely be a filename stored in the database. Since its value is of no use to
+a FileInput it is ignored.
+
+>>> w = FileInput()
+
+# No file was uploaded and no initial data.
+>>> w._has_changed(u'', None)
+False
+
+# A file was uploaded and no initial data.
+>>> w._has_changed(u'', {'filename': 'resume.txt', 'content': 'My resume'})
+True
+
+# A file was not uploaded, but there is initial data
+>>> w._has_changed(u'resume.txt', None)
+False
+
+# A file was uploaded and there is initial data (file identity is not dealt
+# with here)
+>>> w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})
+True
+
# Textarea Widget #############################################################
>>> w = Textarea()
@@ -292,6 +316,21 @@ checkboxes).
>>> w.value_from_datadict({}, {}, 'testing')
False
+>>> w._has_changed(None, None)
+False
+>>> w._has_changed(None, u'')
+False
+>>> w._has_changed(u'', None)
+False
+>>> w._has_changed(u'', u'')
+False
+>>> w._has_changed(False, u'on')
+True
+>>> w._has_changed(True, u'on')
+False
+>>> w._has_changed(True, u'')
+True
+
# Select Widget ###############################################################
>>> w = Select()
@@ -419,6 +458,35 @@ over multiple times without getting consumed:
<option value="4">4</option>
</select>
+Choices can be nested one level in order to create HTML optgroups:
+>>> w.choices=(('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
+>>> print w.render('nestchoice', None)
+<select name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>
+
+>>> print w.render('nestchoice', 'outer1')
+<select name="nestchoice">
+<option value="outer1" selected="selected">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>
+
+>>> print w.render('nestchoice', 'inner1')
+<select name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1" selected="selected">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>
+
# NullBooleanSelect Widget ####################################################
>>> w = NullBooleanSelect()
@@ -573,6 +641,58 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
+# Test the usage of _has_changed
+>>> w._has_changed(None, None)
+False
+>>> w._has_changed([], None)
+False
+>>> w._has_changed(None, [u'1'])
+True
+>>> w._has_changed([1, 2], [u'1', u'2'])
+False
+>>> w._has_changed([1, 2], [u'1'])
+True
+>>> w._has_changed([1, 2], [u'1', u'3'])
+True
+
+# Choices can be nested one level in order to create HTML optgroups:
+>>> w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
+>>> print w.render('nestchoice', None)
+<select multiple="multiple" name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>
+
+>>> print w.render('nestchoice', ['outer1'])
+<select multiple="multiple" name="nestchoice">
+<option value="outer1" selected="selected">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>
+
+>>> print w.render('nestchoice', ['inner1'])
+<select multiple="multiple" name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1" selected="selected">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>
+
+>>> print w.render('nestchoice', ['outer1', 'inner2'])
+<select multiple="multiple" name="nestchoice">
+<option value="outer1" selected="selected">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2" selected="selected">Inner 2</option>
+</optgroup>
+</select>
+
# RadioSelect Widget ##########################################################
>>> w = RadioSelect()
@@ -871,6 +991,20 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<li><label><input type="checkbox" name="escape" value="good" /> you &gt; me</label></li>
</ul>
+# Test the usage of _has_changed
+>>> w._has_changed(None, None)
+False
+>>> w._has_changed([], None)
+False
+>>> w._has_changed(None, [u'1'])
+True
+>>> w._has_changed([1, 2], [u'1', u'2'])
+False
+>>> w._has_changed([1, 2], [u'1'])
+True
+>>> w._has_changed([1, 2], [u'1', u'3'])
+True
+
# Unicode choices are correctly rendered as HTML
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
@@ -895,6 +1029,25 @@ u'<input id="foo_0" type="text" class="big" value="john" name="name_0" /><br /><
>>> w.render('name', ['john', 'lennon'])
u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />'
+>>> w = MyMultiWidget(widgets=(TextInput(), TextInput()))
+
+# test with no initial data
+>>> w._has_changed(None, [u'john', u'lennon'])
+True
+
+# test when the data is the same as initial
+>>> w._has_changed(u'john__lennon', [u'john', u'lennon'])
+False
+
+# test when the first widget's data has changed
+>>> w._has_changed(u'john__lennon', [u'alfred', u'lennon'])
+True
+
+# test when the last widget's data has changed. this ensures that it is not
+# short circuiting while testing the widgets.
+>>> w._has_changed(u'john__lennon', [u'john', u'denver'])
+True
+
# SplitDateTimeWidget #########################################################
>>> w = SplitDateTimeWidget()
@@ -913,6 +1066,11 @@ included on both widgets.
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />'
+>>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:40:00'])
+False
+>>> w._has_changed(datetime.datetime(2008, 5, 5, 12, 40, 00), [u'2008-05-05', u'12:41:00'])
+True
+
# DateTimeInput ###############################################################
>>> w = DateTimeInput()