Migrated forms (minus localflavor) doctests. A huge thanks to Daniel Lindsley for the patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14570 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
8da8d6c586
commit
8bafde1229
2
AUTHORS
2
AUTHORS
|
@ -309,7 +309,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
limodou
|
||||
Philip Lindborg <philip.lindborg@gmail.com>
|
||||
Simon Litchfield <simon@quo.com.au>
|
||||
Daniel Lindsley <polarcowz@gmail.com>
|
||||
Daniel Lindsley <daniel@toastdriven.com>
|
||||
Trey Long <trey@ktrl.com>
|
||||
Laurent Luce <http://www.laurentluce.com>
|
||||
Martin Mahner <http://www.mahner.org/>
|
||||
|
|
|
@ -1,399 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
tests = r"""
|
||||
>>> from django.forms import *
|
||||
>>> from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
# CharField ###################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s'
|
||||
>>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s'
|
||||
>>> f = CharField(min_length=5, max_length=10, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('1234')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'LENGTH 4, MIN LENGTH 5']
|
||||
>>> f.clean('12345678901')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'LENGTH 11, MAX LENGTH 10']
|
||||
|
||||
# IntegerField ################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['min_value'] = 'MIN VALUE IS %(limit_value)s'
|
||||
>>> e['max_value'] = 'MAX VALUE IS %(limit_value)s'
|
||||
>>> f = IntegerField(min_value=5, max_value=10, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean('4')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MIN VALUE IS 5']
|
||||
>>> f.clean('11')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MAX VALUE IS 10']
|
||||
|
||||
# FloatField ##################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['min_value'] = 'MIN VALUE IS %(limit_value)s'
|
||||
>>> e['max_value'] = 'MAX VALUE IS %(limit_value)s'
|
||||
>>> f = FloatField(min_value=5, max_value=10, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean('4')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MIN VALUE IS 5']
|
||||
>>> f.clean('11')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MAX VALUE IS 10']
|
||||
|
||||
# DecimalField ################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['min_value'] = 'MIN VALUE IS %(limit_value)s'
|
||||
>>> e['max_value'] = 'MAX VALUE IS %(limit_value)s'
|
||||
>>> e['max_digits'] = 'MAX DIGITS IS %s'
|
||||
>>> e['max_decimal_places'] = 'MAX DP IS %s'
|
||||
>>> e['max_whole_digits'] = 'MAX DIGITS BEFORE DP IS %s'
|
||||
>>> f = DecimalField(min_value=5, max_value=10, error_messages=e)
|
||||
>>> f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean('4')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MIN VALUE IS 5']
|
||||
>>> f.clean('11')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MAX VALUE IS 10']
|
||||
>>> f2.clean('123.45')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MAX DIGITS IS 4']
|
||||
>>> f2.clean('1.234')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MAX DP IS 2']
|
||||
>>> f2.clean('123.4')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'MAX DIGITS BEFORE DP IS 2']
|
||||
|
||||
# DateField ###################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> f = DateField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
|
||||
# TimeField ###################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> f = TimeField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
|
||||
# DateTimeField ###############################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> f = DateTimeField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
|
||||
# RegexField ##################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s'
|
||||
>>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s'
|
||||
>>> f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abcde')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean('1234')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'LENGTH 4, MIN LENGTH 5']
|
||||
>>> f.clean('12345678901')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'LENGTH 11, MAX LENGTH 10']
|
||||
|
||||
# EmailField ##################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s'
|
||||
>>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s'
|
||||
>>> f = EmailField(min_length=8, max_length=10, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abcdefgh')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean('a@b.com')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'LENGTH 7, MIN LENGTH 8']
|
||||
>>> f.clean('aye@bee.com')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'LENGTH 11, MAX LENGTH 10']
|
||||
|
||||
# FileField ##################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['missing'] = 'MISSING'
|
||||
>>> e['empty'] = 'EMPTY FILE'
|
||||
>>> f = FileField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean(SimpleUploadedFile('name', None))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'EMPTY FILE']
|
||||
>>> f.clean(SimpleUploadedFile('name', ''))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'EMPTY FILE']
|
||||
|
||||
# URLField ##################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID'
|
||||
>>> e['invalid_link'] = 'INVALID LINK'
|
||||
>>> f = URLField(verify_exists=True, error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('abc.c')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID']
|
||||
>>> f.clean('http://www.broken.djangoproject.com')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID LINK']
|
||||
|
||||
# BooleanField ################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> f = BooleanField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
|
||||
# ChoiceField #################################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE'
|
||||
>>> f = ChoiceField(choices=[('a', 'aye')], error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('b')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'b IS INVALID CHOICE']
|
||||
|
||||
# MultipleChoiceField #########################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE'
|
||||
>>> e['invalid_list'] = 'NOT A LIST'
|
||||
>>> f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('b')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'NOT A LIST']
|
||||
>>> f.clean(['b'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'b IS INVALID CHOICE']
|
||||
|
||||
# SplitDateTimeField ##########################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid_date'] = 'INVALID DATE'
|
||||
>>> e['invalid_time'] = 'INVALID TIME'
|
||||
>>> f = SplitDateTimeField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean(['a', 'b'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID DATE', u'INVALID TIME']
|
||||
|
||||
# IPAddressField ##############################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid'] = 'INVALID IP ADDRESS'
|
||||
>>> f = IPAddressField(error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('127.0.0')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID IP ADDRESS']
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Create choices for the model choice field tests below.
|
||||
|
||||
>>> from regressiontests.forms.models import ChoiceModel
|
||||
>>> ChoiceModel.objects.create(pk=1, name='a')
|
||||
<ChoiceModel: ChoiceModel object>
|
||||
>>> ChoiceModel.objects.create(pk=2, name='b')
|
||||
<ChoiceModel: ChoiceModel object>
|
||||
>>> ChoiceModel.objects.create(pk=3, name='c')
|
||||
<ChoiceModel: ChoiceModel object>
|
||||
|
||||
# ModelChoiceField ############################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid_choice'] = 'INVALID CHOICE'
|
||||
>>> f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('4')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'INVALID CHOICE']
|
||||
|
||||
# ModelMultipleChoiceField ####################################################
|
||||
|
||||
>>> e = {'required': 'REQUIRED'}
|
||||
>>> e['invalid_choice'] = '%s IS INVALID CHOICE'
|
||||
>>> e['list'] = 'NOT A LIST OF VALUES'
|
||||
>>> f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'REQUIRED']
|
||||
>>> f.clean('3')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'NOT A LIST OF VALUES']
|
||||
>>> f.clean(['4'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'4 IS INVALID CHOICE']
|
||||
|
||||
# Subclassing ErrorList #######################################################
|
||||
|
||||
>>> from django.utils.safestring import mark_safe
|
||||
>>>
|
||||
>>> class TestForm(Form):
|
||||
... first_name = CharField()
|
||||
... last_name = CharField()
|
||||
... birthday = DateField()
|
||||
...
|
||||
... def clean(self):
|
||||
... raise ValidationError("I like to be awkward.")
|
||||
...
|
||||
>>> class CustomErrorList(util.ErrorList):
|
||||
... def __unicode__(self):
|
||||
... return self.as_divs()
|
||||
... def as_divs(self):
|
||||
... if not self: return u''
|
||||
... return mark_safe(u'<div class="error">%s</div>'
|
||||
... % ''.join([u'<p>%s</p>' % e for e in self]))
|
||||
...
|
||||
|
||||
This form should print errors the default way.
|
||||
|
||||
>>> form1 = TestForm({'first_name': 'John'})
|
||||
>>> print form1['last_name'].errors
|
||||
<ul class="errorlist"><li>This field is required.</li></ul>
|
||||
>>> print form1.errors['__all__']
|
||||
<ul class="errorlist"><li>I like to be awkward.</li></ul>
|
||||
|
||||
This one should wrap error groups in the customized way.
|
||||
|
||||
>>> form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList)
|
||||
>>> print form2['last_name'].errors
|
||||
<div class="error"><p>This field is required.</p></div>
|
||||
>>> print form2.errors['__all__']
|
||||
<div class="error"><p>I like to be awkward.</p></div>
|
||||
|
||||
"""
|
File diff suppressed because it is too large
Load Diff
|
@ -1,762 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.test.testcases import TestCase
|
||||
from django.forms.forms import Form
|
||||
from django.forms.fields import CharField, IntegerField
|
||||
from django.forms.formsets import formset_factory
|
||||
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_NUM_FORMS" />
|
||||
<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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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>
|
||||
|
||||
Make sure retrieving an empty form works, and it shows up in the form list
|
||||
|
||||
>>> formset.empty_form.empty_permitted
|
||||
True
|
||||
>>> print formset.empty_form.as_ul()
|
||||
<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>
|
||||
|
||||
# FormSets with deletion ######################################################
|
||||
|
||||
We can easily add deletion ability to a FormSet with an argument 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_NUM_FORMS': '0', # 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'}]
|
||||
|
||||
If we fill a form with something and then we check the can_delete checkbox for
|
||||
that form, that form's errors should not make the entire formset invalid since
|
||||
it's going to be deleted.
|
||||
|
||||
>>> class CheckForm(Form):
|
||||
... field = IntegerField(min_value=100)
|
||||
|
||||
>>> data = {
|
||||
... 'check-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
... 'check-INITIAL_FORMS': '2', # the number of forms with initial data
|
||||
... 'check-MAX_NUM_FORMS': '0', # max number of forms
|
||||
... 'check-0-field': '200',
|
||||
... 'check-0-DELETE': '',
|
||||
... 'check-1-field': '50',
|
||||
... 'check-1-DELETE': 'on',
|
||||
... 'check-2-field': '',
|
||||
... 'check-2-DELETE': '',
|
||||
... }
|
||||
>>> CheckFormSet = formset_factory(CheckForm, can_delete=True)
|
||||
>>> formset = CheckFormSet(data, prefix='check')
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
If we remove the deletion flag now we will have our validation back.
|
||||
|
||||
>>> data['check-1-DELETE'] = ''
|
||||
>>> formset = CheckFormSet(data, prefix='check')
|
||||
>>> formset.is_valid()
|
||||
False
|
||||
|
||||
Should be able to get deleted_forms from a valid formset even if a
|
||||
deleted form would have been invalid.
|
||||
|
||||
>>> class Person(Form):
|
||||
... name = CharField()
|
||||
|
||||
>>> PeopleForm = formset_factory(
|
||||
... form=Person,
|
||||
... can_delete=True)
|
||||
|
||||
>>> p = PeopleForm(
|
||||
... {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
|
||||
... 'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
|
||||
... 'form-MAX_NUM_FORMS': 1})
|
||||
|
||||
>>> p.is_valid()
|
||||
True
|
||||
>>> len(p.deleted_forms)
|
||||
1
|
||||
|
||||
# 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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'}
|
||||
|
||||
Ordering should work with blank fieldsets.
|
||||
|
||||
>>> data = {
|
||||
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
... 'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
... }
|
||||
|
||||
>>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
>>> for form in formset.ordered_forms:
|
||||
... print form.cleaned_data
|
||||
|
||||
# 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_NUM_FORMS': '0', # 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'}]
|
||||
|
||||
Should be able to get ordered forms from a valid formset even if a
|
||||
deleted form would have been invalid.
|
||||
|
||||
>>> class Person(Form):
|
||||
... name = CharField()
|
||||
|
||||
>>> PeopleForm = formset_factory(
|
||||
... form=Person,
|
||||
... can_delete=True,
|
||||
... can_order=True)
|
||||
|
||||
>>> p = PeopleForm(
|
||||
... {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
|
||||
... 'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
|
||||
... 'form-MAX_NUM_FORMS': 1})
|
||||
|
||||
>>> p.is_valid()
|
||||
True
|
||||
>>> p.ordered_forms
|
||||
[]
|
||||
|
||||
# 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_NUM_FORMS': '0', # 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_NUM_FORMS': '0', # 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.
|
||||
|
||||
# When not passed, max_num will take its default value of None, i.e. unlimited
|
||||
# number of forms, only controlled by the value of the extra parameter.
|
||||
|
||||
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3)
|
||||
>>> 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>
|
||||
<tr><th><label for="id_form-2-name">Name:</label></th><td><input type="text" name="form-2-name" id="id_form-2-name" /></td></tr>
|
||||
|
||||
# If max_num is 0 then no form is rendered at all.
|
||||
|
||||
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=0)
|
||||
>>> formset = LimitedFavoriteDrinkFormSet()
|
||||
>>> for form in formset.forms:
|
||||
... print form
|
||||
|
||||
>>> 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 that max_num has no effect when extra is less than max_num.
|
||||
|
||||
>>> 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
|
||||
|
||||
# When not passed, max_num will take its default value of None, i.e. unlimited
|
||||
# number of forms, only controlled by the values of the initial and extra
|
||||
# parameters.
|
||||
|
||||
>>> initial = [
|
||||
... {'name': 'Fernet and Coke'},
|
||||
... ]
|
||||
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1)
|
||||
>>> 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="Fernet and Coke" 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>
|
||||
|
||||
# If max_num is 0 then no form is rendered at all, even if extra and initial
|
||||
# are specified.
|
||||
|
||||
>>> initial = [
|
||||
... {'name': 'Fernet and Coke'},
|
||||
... {'name': 'Bloody Mary'},
|
||||
... ]
|
||||
>>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
|
||||
>>> formset = LimitedFavoriteDrinkFormSet(initial=initial)
|
||||
>>> for form in formset.forms:
|
||||
... print form
|
||||
|
||||
# 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'
|
||||
|
||||
# Regression test for #12878 #################################################
|
||||
|
||||
>>> data = {
|
||||
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
... 'drinks-MAX_NUM_FORMS': '0', # 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
|
||||
>>> print formset.non_form_errors()
|
||||
<ul class="errorlist"><li>You may only specify a drink once.</li></ul>
|
||||
|
||||
"""
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '1', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
}
|
||||
|
||||
class Choice(Form):
|
||||
choice = CharField()
|
||||
votes = IntegerField()
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice)
|
||||
|
||||
class FormsetAsFooTests(TestCase):
|
||||
def test_as_table(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
|
||||
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
|
||||
|
||||
def test_as_p(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
|
||||
<p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
|
||||
|
||||
def test_as_ul(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
|
||||
|
|
@ -51,7 +51,7 @@ class BETests(TestCase):
|
|||
self.assertEqual(u'0412.34.56.78', f.clean('0412.34.56.78'))
|
||||
self.assertEqual(u'012345678', f.clean('012345678'))
|
||||
self.assertEqual(u'0412345678', f.clean('0412345678'))
|
||||
err_message = "[u'Enter a valid phone number in one of the formats 0x xxx xx xx, 0xx xx xx xx, 04xx xx xx xx, 0x/xxx.xx.xx, 0xx/xx.xx.xx, 04xx/xx.xx.xx, 0xxxxxxxx, 04xxxxxxxx, 0x.xxx.xx.xx, 0xx.xx.xx.xx, 04xx.xx.xx.xx.']"
|
||||
err_message = "[u'Enter a valid phone number in one of the formats 0x xxx xx xx, 0xx xx xx xx, 04xx xx xx xx, 0x/xxx.xx.xx, 0xx/xx.xx.xx, 04xx/xx.xx.xx, 0x.xxx.xx.xx, 0xx.xx.xx.xx, 04xx.xx.xx.xx, 0xxxxxxxx or 04xxxxxxxx.']"
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '01234567')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '12/345.67.89')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '012/345.678.90')
|
||||
|
@ -75,7 +75,7 @@ class BETests(TestCase):
|
|||
self.assertEqual(u'012345678', f.clean('012345678'))
|
||||
self.assertEqual(u'0412345678', f.clean('0412345678'))
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
err_message = "[u'Enter a valid phone number in one of the formats 0x xxx xx xx, 0xx xx xx xx, 04xx xx xx xx, 0x/xxx.xx.xx, 0xx/xx.xx.xx, 04xx/xx.xx.xx, 0xxxxxxxx, 04xxxxxxxx, 0x.xxx.xx.xx, 0xx.xx.xx.xx, 04xx.xx.xx.xx.']"
|
||||
err_message = "[u'Enter a valid phone number in one of the formats 0x xxx xx xx, 0xx xx xx xx, 04xx xx xx xx, 0x/xxx.xx.xx, 0xx/xx.xx.xx, 04xx/xx.xx.xx, 0x.xxx.xx.xx, 0xx.xx.xx.xx, 04xx.xx.xx.xx, 0xxxxxxxx or 04xxxxxxxx.']"
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '01234567')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '12/345.67.89')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '012/345.678.90')
|
||||
|
@ -85,10 +85,10 @@ class BETests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '012/34 56 789')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, err_message, f.clean, '012.34 56 789')
|
||||
|
||||
def test_phone_number_field(self):
|
||||
def test_region_field(self):
|
||||
w = BERegionSelect()
|
||||
self.assertEqual(u'<select name="regions">\n<option value="BRU">Brussels Capital Region</option>\n<option value="VLG" selected="selected">Flemish Region</option>\n<option value="WAL">Wallonia</option>\n</select>', w.render('regions', 'VLG'))
|
||||
|
||||
def test_phone_number_field(self):
|
||||
def test_province_field(self):
|
||||
w = BEProvinceSelect()
|
||||
self.assertEqual(u'<select name="provinces">\n<option value="VAN">Antwerp</option>\n<option value="BRU">Brussels</option>\n<option value="VOV">East Flanders</option>\n<option value="VBR">Flemish Brabant</option>\n<option value="WHT">Hainaut</option>\n<option value="WLG" selected="selected">Liege</option>\n<option value="VLI">Limburg</option>\n<option value="WLX">Luxembourg</option>\n<option value="WNA">Namur</option>\n<option value="WBR">Walloon Brabant</option>\n<option value="VWV">West Flanders</option>\n</select>', w.render('provinces', 'WLG'))
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from extra import tests as extra_tests
|
||||
from forms import tests as form_tests
|
||||
from error_messages import tests as custom_error_message_tests
|
||||
from localflavor.ar import tests as localflavor_ar_tests
|
||||
from localflavor.at import tests as localflavor_at_tests
|
||||
from localflavor.au import tests as localflavor_au_tests
|
||||
|
@ -32,25 +29,10 @@ from localflavor.uk import tests as localflavor_uk_tests
|
|||
from localflavor.us import tests as localflavor_us_tests
|
||||
from localflavor.uy import tests as localflavor_uy_tests
|
||||
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
|
||||
|
||||
|
||||
from formsets import FormsetAsFooTests
|
||||
from fields import FieldsTests
|
||||
from validators import TestFieldWithValidators
|
||||
from widgets import WidgetTests, ClearableFileInputTests
|
||||
from localflavor.be import BETests
|
||||
|
||||
from input_formats import *
|
||||
|
||||
__test__ = {
|
||||
'extra_tests': extra_tests,
|
||||
'form_tests': form_tests,
|
||||
'custom_error_message_tests': custom_error_message_tests,
|
||||
'localflavor_ar_tests': localflavor_ar_tests,
|
||||
'localflavor_at_tests': localflavor_at_tests,
|
||||
'localflavor_au_tests': localflavor_au_tests,
|
||||
|
@ -80,11 +62,6 @@ __test__ = {
|
|||
'localflavor_us_tests': localflavor_us_tests,
|
||||
'localflavor_uy_tests': localflavor_uy_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,
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
|
@ -1,385 +0,0 @@
|
|||
# -*- 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>
|
||||
|
||||
# Regression check for #12879: specifying the same CSS or JS file
|
||||
# multiple times in a single Media instance should result in that file
|
||||
# only being included once.
|
||||
>>> class MyWidget4(TextInput):
|
||||
... class Media:
|
||||
... css = {'all': ('/path/to/css1', '/path/to/css1')}
|
||||
... js = ('/path/to/js1', '/path/to/js1')
|
||||
|
||||
>>> w4 = MyWidget4()
|
||||
>>> print w4.media
|
||||
<link href="/path/to/css1" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="/path/to/js1"></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>
|
||||
|
||||
# Media works in templates
|
||||
>>> from django.template import Template, Context
|
||||
>>> Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3}))
|
||||
u'<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><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" />'
|
||||
|
||||
>>> settings.MEDIA_URL = ORIGINAL_MEDIA_URL
|
||||
"""
|
|
@ -1,14 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from django.db import models
|
||||
# Can't import as "forms" due to implementation details in the test suite (the
|
||||
# current file is called "forms" and is already imported).
|
||||
from django import forms as django_forms
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.test import TestCase
|
||||
|
||||
temp_storage_location = tempfile.mkdtemp()
|
||||
temp_storage = FileSystemStorage(location=temp_storage_location)
|
||||
|
@ -56,182 +51,11 @@ class ChoiceFieldModel(models.Model):
|
|||
multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int',
|
||||
default=lambda: [1])
|
||||
|
||||
class ChoiceFieldForm(django_forms.ModelForm):
|
||||
class Meta:
|
||||
model = ChoiceFieldModel
|
||||
|
||||
class FileModel(models.Model):
|
||||
file = models.FileField(storage=temp_storage, upload_to='tests')
|
||||
|
||||
class FileForm(django_forms.Form):
|
||||
file1 = django_forms.FileField()
|
||||
|
||||
class Group(models.Model):
|
||||
name = models.CharField(max_length=10)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.name
|
||||
|
||||
class TestTicket12510(TestCase):
|
||||
''' It is not necessary to generate choices for ModelChoiceField (regression test for #12510). '''
|
||||
def setUp(self):
|
||||
self.groups = [Group.objects.create(name=name) for name in 'abc']
|
||||
|
||||
def test_choices_not_fetched_when_not_rendering(self):
|
||||
def test():
|
||||
field = django_forms.ModelChoiceField(Group.objects.order_by('-name'))
|
||||
self.assertEqual('a', field.clean(self.groups[0].pk).name)
|
||||
# only one query is required to pull the model from DB
|
||||
self.assertNumQueries(1, test)
|
||||
|
||||
class ModelFormCallableModelDefault(TestCase):
|
||||
def test_no_empty_option(self):
|
||||
"If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)."
|
||||
option = ChoiceOptionModel.objects.create(name='default')
|
||||
|
||||
choices = list(ChoiceFieldForm().fields['choice'].choices)
|
||||
self.assertEquals(len(choices), 1)
|
||||
self.assertEquals(choices[0], (option.pk, unicode(option)))
|
||||
|
||||
def test_callable_initial_value(self):
|
||||
"The initial value for a callable default returning a queryset is the pk (refs #13769)"
|
||||
obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
|
||||
obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
|
||||
obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
|
||||
self.assertEquals(ChoiceFieldForm().as_p(), """<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice" value="1" id="initial-id_choice" /></p>
|
||||
<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" id="id_choice_int">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice_int" value="1" id="initial-id_choice_int" /></p>
|
||||
<p><label for="id_multi_choice">Multi choice:</label> <select multiple="multiple" name="multi_choice" id="id_multi_choice">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice" value="1" id="initial-id_multi_choice_0" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>
|
||||
<p><label for="id_multi_choice_int">Multi choice int:</label> <select multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice_int" value="1" id="initial-id_multi_choice_int_0" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>""")
|
||||
|
||||
def test_initial_instance_value(self):
|
||||
"Initial instances for model fields may also be instances (refs #7287)"
|
||||
obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
|
||||
obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
|
||||
obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
|
||||
self.assertEquals(ChoiceFieldForm(initial={
|
||||
'choice': obj2,
|
||||
'choice_int': obj2,
|
||||
'multi_choice': [obj2,obj3],
|
||||
'multi_choice_int': ChoiceOptionModel.objects.exclude(name="default"),
|
||||
}).as_p(), """<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice" value="2" id="initial-id_choice" /></p>
|
||||
<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" id="id_choice_int">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice_int" value="2" id="initial-id_choice_int" /></p>
|
||||
<p><label for="id_multi_choice">Multi choice:</label> <select multiple="multiple" name="multi_choice" id="id_multi_choice">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3" selected="selected">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice" value="2" id="initial-id_multi_choice_0" />
|
||||
<input type="hidden" name="initial-multi_choice" value="3" id="initial-id_multi_choice_1" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>
|
||||
<p><label for="id_multi_choice_int">Multi choice int:</label> <select multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3" selected="selected">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice_int" value="2" id="initial-id_multi_choice_int_0" />
|
||||
<input type="hidden" name="initial-multi_choice_int" value="3" id="initial-id_multi_choice_int_1" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>""")
|
||||
|
||||
|
||||
__test__ = {'API_TESTS': """
|
||||
>>> from django.forms.models import ModelForm
|
||||
>>> from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
# FileModel with unicode filename and data #########################
|
||||
>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False)
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'file1': <SimpleUploadedFile: 我隻氣墊船裝滿晒鱔.txt (text/plain)>}
|
||||
>>> m = FileModel.objects.create(file=f.cleaned_data['file1'])
|
||||
|
||||
# It's enough that m gets created without error. Preservation of the exotic name is checked
|
||||
# in a file_uploads test; it's hard to do that correctly with doctest's unicode issues. So
|
||||
# we create and then immediately delete m so as to not leave the exotically named file around
|
||||
# for shutil.rmtree (on Windows) to have trouble with later.
|
||||
>>> m.delete()
|
||||
|
||||
# Boundary conditions on a PostitiveIntegerField #########################
|
||||
>>> class BoundaryForm(ModelForm):
|
||||
... class Meta:
|
||||
... model = BoundaryModel
|
||||
>>> f = BoundaryForm({'positive_integer': 100})
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f = BoundaryForm({'positive_integer': 0})
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f = BoundaryForm({'positive_integer': -100})
|
||||
>>> f.is_valid()
|
||||
False
|
||||
|
||||
# Formfield initial values ########
|
||||
If the model has default values for some fields, they are used as the formfield
|
||||
initial values.
|
||||
>>> class DefaultsForm(ModelForm):
|
||||
... class Meta:
|
||||
... model = Defaults
|
||||
>>> DefaultsForm().fields['name'].initial
|
||||
u'class default value'
|
||||
>>> DefaultsForm().fields['def_date'].initial
|
||||
datetime.date(1980, 1, 1)
|
||||
>>> DefaultsForm().fields['value'].initial
|
||||
42
|
||||
>>> r1 = DefaultsForm()['callable_default'].as_widget()
|
||||
>>> r2 = DefaultsForm()['callable_default'].as_widget()
|
||||
>>> r1 == r2
|
||||
False
|
||||
|
||||
In a ModelForm that is passed an instance, the initial values come from the
|
||||
instance's values, not the model's defaults.
|
||||
>>> foo_instance = Defaults(name=u'instance value', def_date=datetime.date(1969, 4, 4), value=12)
|
||||
>>> instance_form = DefaultsForm(instance=foo_instance)
|
||||
>>> instance_form.initial['name']
|
||||
u'instance value'
|
||||
>>> instance_form.initial['def_date']
|
||||
datetime.date(1969, 4, 4)
|
||||
>>> instance_form.initial['value']
|
||||
12
|
||||
|
||||
>>> from django.forms import CharField
|
||||
>>> class ExcludingForm(ModelForm):
|
||||
... name = CharField(max_length=255)
|
||||
... class Meta:
|
||||
... model = Defaults
|
||||
... exclude = ['name', 'callable_default']
|
||||
>>> f = ExcludingForm({'name': u'Hello', 'value': 99, 'def_date': datetime.date(1999, 3, 2)})
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data['name']
|
||||
u'Hello'
|
||||
>>> obj = f.save()
|
||||
>>> obj.name
|
||||
u'class default value'
|
||||
>>> obj.value
|
||||
99
|
||||
>>> obj.def_date
|
||||
datetime.date(1999, 3, 2)
|
||||
>>> shutil.rmtree(temp_storage_location)
|
||||
|
||||
|
||||
"""}
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Tests to prevent against recurrences of earlier bugs.
|
||||
|
||||
tests = r"""
|
||||
It should be possible to re-use attribute dictionaries (#3810)
|
||||
>>> from django.forms import *
|
||||
>>> extra_attrs = {'class': 'special'}
|
||||
>>> class TestForm(Form):
|
||||
... f1 = CharField(max_length=10, widget=TextInput(attrs=extra_attrs))
|
||||
... f2 = CharField(widget=TextInput(attrs=extra_attrs))
|
||||
>>> TestForm(auto_id=False).as_p()
|
||||
u'<p>F1: <input type="text" class="special" name="f1" maxlength="10" /></p>\n<p>F2: <input type="text" class="special" name="f2" /></p>'
|
||||
|
||||
#######################
|
||||
# Tests for form i18n #
|
||||
#######################
|
||||
There were some problems with form translations in #3600
|
||||
|
||||
>>> from django.utils.translation import ugettext_lazy, activate, deactivate
|
||||
>>> class SomeForm(Form):
|
||||
... username = CharField(max_length=10, label=ugettext_lazy('Username'))
|
||||
>>> f = SomeForm()
|
||||
>>> print f.as_p()
|
||||
<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
|
||||
|
||||
Translations are done at rendering time, so multi-lingual apps can define forms
|
||||
early and still send back the right translation.
|
||||
|
||||
>>> activate('de')
|
||||
>>> print f.as_p()
|
||||
<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
|
||||
>>> activate('pl')
|
||||
>>> f.as_p()
|
||||
u'<p><label for="id_username">Nazwa u\u017cytkownika:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>'
|
||||
>>> deactivate()
|
||||
|
||||
There was some problems with form translations in #5216
|
||||
>>> class SomeForm(Form):
|
||||
... field_1 = CharField(max_length=10, label=ugettext_lazy('field_1'))
|
||||
... field_2 = CharField(max_length=10, label=ugettext_lazy('field_2'), widget=TextInput(attrs={'id': 'field_2_id'}))
|
||||
>>> f = SomeForm()
|
||||
>>> print f['field_1'].label_tag()
|
||||
<label for="id_field_1">field_1</label>
|
||||
>>> print f['field_2'].label_tag()
|
||||
<label for="field_2_id">field_2</label>
|
||||
|
||||
Unicode decoding problems...
|
||||
>>> GENDERS = ((u'\xc5', u'En tied\xe4'), (u'\xf8', u'Mies'), (u'\xdf', u'Nainen'))
|
||||
>>> class SomeForm(Form):
|
||||
... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf')
|
||||
>>> f = SomeForm()
|
||||
>>> f.as_p()
|
||||
u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
|
||||
|
||||
Testing choice validation with UTF-8 bytestrings as input (these are the
|
||||
Russian abbreviations "мес." and "шт.".
|
||||
|
||||
>>> UNITS = (('\xd0\xbc\xd0\xb5\xd1\x81.', '\xd0\xbc\xd0\xb5\xd1\x81.'), ('\xd1\x88\xd1\x82.', '\xd1\x88\xd1\x82.'))
|
||||
>>> f = ChoiceField(choices=UNITS)
|
||||
>>> f.clean(u'\u0448\u0442.')
|
||||
u'\u0448\u0442.'
|
||||
>>> f.clean('\xd1\x88\xd1\x82.')
|
||||
u'\u0448\u0442.'
|
||||
|
||||
Translated error messages used to be buggy.
|
||||
>>> activate('ru')
|
||||
>>> f = SomeForm({})
|
||||
>>> f.as_p()
|
||||
u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
|
||||
>>> deactivate()
|
||||
|
||||
Deep copying translated text shouldn't raise an error
|
||||
>>> from django.utils.translation import gettext_lazy
|
||||
>>> class CopyForm(Form):
|
||||
... degree = IntegerField(widget=Select(choices=((1, gettext_lazy('test')),)))
|
||||
>>> f = CopyForm()
|
||||
|
||||
#######################
|
||||
# Miscellaneous Tests #
|
||||
#######################
|
||||
|
||||
There once was a problem with Form fields called "data". Let's make sure that
|
||||
doesn't come back.
|
||||
>>> class DataForm(Form):
|
||||
... data = CharField(max_length=10)
|
||||
>>> f = DataForm({'data': 'xyzzy'})
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data
|
||||
{'data': u'xyzzy'}
|
||||
|
||||
A form with *only* hidden fields that has errors is going to be very unusual.
|
||||
But we can try to make sure it doesn't generate invalid XHTML. In this case,
|
||||
the as_p() method is the tricky one, since error lists cannot be nested
|
||||
(validly) inside p elements.
|
||||
|
||||
>>> class HiddenForm(Form):
|
||||
... data = IntegerField(widget=HiddenInput)
|
||||
>>> f = HiddenForm({})
|
||||
>>> f.as_p()
|
||||
u'<ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul>\n<p> <input type="hidden" name="data" id="id_data" /></p>'
|
||||
>>> f.as_table()
|
||||
u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>'
|
||||
|
||||
###################################################
|
||||
# Tests for XSS vulnerabilities in error messages #
|
||||
###################################################
|
||||
|
||||
# The forms layer doesn't escape input values directly because error messages
|
||||
# might be presented in non-HTML contexts. Instead, the message is just marked
|
||||
# for escaping by the template engine. So we'll need to construct a little
|
||||
# silly template to trigger the escaping.
|
||||
|
||||
>>> from django.template import Template, Context
|
||||
>>> t = Template('{{ form.errors }}')
|
||||
|
||||
>>> class SomeForm(Form):
|
||||
... field = ChoiceField(choices=[('one', 'One')])
|
||||
>>> f = SomeForm({'field': '<script>'})
|
||||
>>> t.render(Context({'form': f}))
|
||||
u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>'
|
||||
|
||||
>>> class SomeForm(Form):
|
||||
... field = MultipleChoiceField(choices=[('one', 'One')])
|
||||
>>> f = SomeForm({'field': ['<script>']})
|
||||
>>> t.render(Context({'form': f}))
|
||||
u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>'
|
||||
|
||||
>>> from regressiontests.forms.models import ChoiceModel
|
||||
>>> class SomeForm(Form):
|
||||
... field = ModelMultipleChoiceField(ChoiceModel.objects.all())
|
||||
>>> f = SomeForm({'field': ['<script>']})
|
||||
>>> t.render(Context({'form': f}))
|
||||
u'<ul class="errorlist"><li>field<ul class="errorlist"><li>"<script>" is not a valid value for a primary key.</li></ul></li></ul>'
|
||||
"""
|
|
@ -0,0 +1,15 @@
|
|||
from error_messages import *
|
||||
from extra import *
|
||||
from fields import FieldsTests
|
||||
from forms import *
|
||||
from formsets import *
|
||||
from input_formats import *
|
||||
from media import *
|
||||
from models import *
|
||||
from regressions import *
|
||||
from util import *
|
||||
from validators import TestFieldWithValidators
|
||||
from widgets import *
|
||||
|
||||
from regressiontests.forms.localflavortests import __test__
|
||||
from regressiontests.forms.localflavortests import BETests, IsraelLocalFlavorTests
|
|
@ -0,0 +1,253 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.forms import *
|
||||
from django.test import TestCase
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import unittest
|
||||
|
||||
class AssertFormErrorsMixin(object):
|
||||
def assertFormErrors(self, expected, the_callable, *args, **kwargs):
|
||||
try:
|
||||
the_callable(*args, **kwargs)
|
||||
self.fail("Testing the 'clean' method on %s failed to raise a ValidationError.")
|
||||
except ValidationError, e:
|
||||
self.assertEqual(e.messages, expected)
|
||||
|
||||
|
||||
class FormsErrorMessagesTestCase(unittest.TestCase, AssertFormErrorsMixin):
|
||||
def test_charfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'min_length': 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s',
|
||||
'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s',
|
||||
}
|
||||
f = CharField(min_length=5, max_length=10, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'LENGTH 4, MIN LENGTH 5'], f.clean, '1234')
|
||||
self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901')
|
||||
|
||||
def test_integerfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'min_value': 'MIN VALUE IS %(limit_value)s',
|
||||
'max_value': 'MAX VALUE IS %(limit_value)s',
|
||||
}
|
||||
f = IntegerField(min_value=5, max_value=10, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4')
|
||||
self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11')
|
||||
|
||||
def test_floatfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'min_value': 'MIN VALUE IS %(limit_value)s',
|
||||
'max_value': 'MAX VALUE IS %(limit_value)s',
|
||||
}
|
||||
f = FloatField(min_value=5, max_value=10, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4')
|
||||
self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11')
|
||||
|
||||
def test_decimalfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'min_value': 'MIN VALUE IS %(limit_value)s',
|
||||
'max_value': 'MAX VALUE IS %(limit_value)s',
|
||||
'max_digits': 'MAX DIGITS IS %s',
|
||||
'max_decimal_places': 'MAX DP IS %s',
|
||||
'max_whole_digits': 'MAX DIGITS BEFORE DP IS %s',
|
||||
}
|
||||
f = DecimalField(min_value=5, max_value=10, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4')
|
||||
self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11')
|
||||
|
||||
f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e)
|
||||
self.assertFormErrors([u'MAX DIGITS IS 4'], f2.clean, '123.45')
|
||||
self.assertFormErrors([u'MAX DP IS 2'], f2.clean, '1.234')
|
||||
self.assertFormErrors([u'MAX DIGITS BEFORE DP IS 2'], f2.clean, '123.4')
|
||||
|
||||
def test_datefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
}
|
||||
f = DateField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
|
||||
def test_timefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
}
|
||||
f = TimeField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
|
||||
def test_datetimefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
}
|
||||
f = DateTimeField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
|
||||
def test_regexfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'min_length': 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s',
|
||||
'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s',
|
||||
}
|
||||
f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abcde')
|
||||
self.assertFormErrors([u'LENGTH 4, MIN LENGTH 5'], f.clean, '1234')
|
||||
self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901')
|
||||
|
||||
def test_emailfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'min_length': 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s',
|
||||
'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s',
|
||||
}
|
||||
f = EmailField(min_length=8, max_length=10, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abcdefgh')
|
||||
self.assertFormErrors([u'LENGTH 7, MIN LENGTH 8'], f.clean, 'a@b.com')
|
||||
self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, 'aye@bee.com')
|
||||
|
||||
def test_filefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'missing': 'MISSING',
|
||||
'empty': 'EMPTY FILE',
|
||||
}
|
||||
f = FileField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc')
|
||||
self.assertFormErrors([u'EMPTY FILE'], f.clean, SimpleUploadedFile('name', None))
|
||||
self.assertFormErrors([u'EMPTY FILE'], f.clean, SimpleUploadedFile('name', ''))
|
||||
|
||||
def test_urlfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID',
|
||||
'invalid_link': 'INVALID LINK',
|
||||
}
|
||||
f = URLField(verify_exists=True, error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID'], f.clean, 'abc.c')
|
||||
self.assertFormErrors([u'INVALID LINK'], f.clean, 'http://www.broken.djangoproject.com')
|
||||
|
||||
def test_booleanfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
}
|
||||
f = BooleanField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
|
||||
def test_choicefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid_choice': '%(value)s IS INVALID CHOICE',
|
||||
}
|
||||
f = ChoiceField(choices=[('a', 'aye')], error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'b IS INVALID CHOICE'], f.clean, 'b')
|
||||
|
||||
def test_multiplechoicefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid_choice': '%(value)s IS INVALID CHOICE',
|
||||
'invalid_list': 'NOT A LIST',
|
||||
}
|
||||
f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'NOT A LIST'], f.clean, 'b')
|
||||
self.assertFormErrors([u'b IS INVALID CHOICE'], f.clean, ['b'])
|
||||
|
||||
def test_splitdatetimefield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid_date': 'INVALID DATE',
|
||||
'invalid_time': 'INVALID TIME',
|
||||
}
|
||||
f = SplitDateTimeField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID DATE', u'INVALID TIME'], f.clean, ['a', 'b'])
|
||||
|
||||
def test_ipaddressfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID IP ADDRESS',
|
||||
}
|
||||
f = IPAddressField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
|
||||
|
||||
def test_subclassing_errorlist(self):
|
||||
class TestForm(Form):
|
||||
first_name = CharField()
|
||||
last_name = CharField()
|
||||
birthday = DateField()
|
||||
|
||||
def clean(self):
|
||||
raise ValidationError("I like to be awkward.")
|
||||
|
||||
class CustomErrorList(util.ErrorList):
|
||||
def __unicode__(self):
|
||||
return self.as_divs()
|
||||
|
||||
def as_divs(self):
|
||||
if not self: return u''
|
||||
return mark_safe(u'<div class="error">%s</div>' % ''.join([u'<p>%s</p>' % e for e in self]))
|
||||
|
||||
# This form should print errors the default way.
|
||||
form1 = TestForm({'first_name': 'John'})
|
||||
self.assertEqual(str(form1['last_name'].errors), '<ul class="errorlist"><li>This field is required.</li></ul>')
|
||||
self.assertEqual(str(form1.errors['__all__']), '<ul class="errorlist"><li>I like to be awkward.</li></ul>')
|
||||
|
||||
# This one should wrap error groups in the customized way.
|
||||
form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList)
|
||||
self.assertEqual(str(form2['last_name'].errors), '<div class="error"><p>This field is required.</p></div>')
|
||||
self.assertEqual(str(form2.errors['__all__']), '<div class="error"><p>I like to be awkward.</p></div>')
|
||||
|
||||
|
||||
class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin):
|
||||
def test_modelchoicefield(self):
|
||||
# Create choices for the model choice field tests below.
|
||||
from regressiontests.forms.models import ChoiceModel
|
||||
c1 = ChoiceModel.objects.create(pk=1, name='a')
|
||||
c2 = ChoiceModel.objects.create(pk=2, name='b')
|
||||
c3 = ChoiceModel.objects.create(pk=3, name='c')
|
||||
|
||||
# ModelChoiceField
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid_choice': 'INVALID CHOICE',
|
||||
}
|
||||
f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID CHOICE'], f.clean, '4')
|
||||
|
||||
# ModelMultipleChoiceField
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid_choice': '%s IS INVALID CHOICE',
|
||||
'list': 'NOT A LIST OF VALUES',
|
||||
}
|
||||
f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'NOT A LIST OF VALUES'], f.clean, '3')
|
||||
self.assertFormErrors([u'4 IS INVALID CHOICE'], f.clean, ['4'])
|
|
@ -1,25 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
tests = r"""
|
||||
>>> from django.forms import *
|
||||
>>> from django.utils.encoding import force_unicode
|
||||
>>> import datetime
|
||||
>>> import time
|
||||
>>> import re
|
||||
>>> from decimal import Decimal
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
import re
|
||||
import time
|
||||
from django.conf import settings
|
||||
from django.forms import *
|
||||
from django.forms.extras import SelectDateWidget
|
||||
from django.forms.util import ErrorList
|
||||
from django.test import TestCase
|
||||
from django.utils import translation
|
||||
from django.utils import unittest
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.encoding import smart_unicode
|
||||
from error_messages import AssertFormErrorsMixin
|
||||
|
||||
###############
|
||||
# Extra stuff #
|
||||
###############
|
||||
|
||||
The forms library comes with some extra, higher-level Field and Widget
|
||||
classes that demonstrate some of the library's abilities.
|
||||
class FormsExtraTestCase(unittest.TestCase, AssertFormErrorsMixin):
|
||||
###############
|
||||
# Extra stuff #
|
||||
###############
|
||||
|
||||
# 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">
|
||||
# The forms library comes with some extra, higher-level Field and Widget
|
||||
def test_selectdate(self):
|
||||
w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'))
|
||||
self.assertEqual(w.render('mydate', ''), """<select name="mydate_month" id="id_mydate_month">
|
||||
<option value="0">---</option>
|
||||
<option value="1">January</option>
|
||||
<option value="2">February</option>
|
||||
|
@ -80,11 +84,10 @@ classes that demonstrate some of the library's abilities.
|
|||
<option value="2014">2014</option>
|
||||
<option value="2015">2015</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>
|
||||
>>> w.render('mydate', None) == w.render('mydate', '')
|
||||
True
|
||||
>>> print w.render('mydate', '2010-04-15')
|
||||
<select name="mydate_month" id="id_mydate_month">
|
||||
</select>""")
|
||||
self.assertEqual(w.render('mydate', None), w.render('mydate', ''))
|
||||
|
||||
self.assertEqual(w.render('mydate', '2010-04-15'), """<select name="mydate_month" id="id_mydate_month">
|
||||
<option value="1">January</option>
|
||||
<option value="2">February</option>
|
||||
<option value="3">March</option>
|
||||
|
@ -142,16 +145,13 @@ True
|
|||
<option value="2014">2014</option>
|
||||
<option value="2015">2015</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>
|
||||
</select>""")
|
||||
|
||||
Accepts a datetime or a string:
|
||||
# Accepts a datetime or a string:
|
||||
self.assertEqual(w.render('mydate', datetime.date(2010, 4, 15)), w.render('mydate', '2010-04-15'))
|
||||
|
||||
>>> w.render('mydate', datetime.date(2010, 4, 15)) == w.render('mydate', '2010-04-15')
|
||||
True
|
||||
|
||||
Invalid dates still render the failed date:
|
||||
>>> print w.render('mydate', '2010-02-31')
|
||||
<select name="mydate_month" id="id_mydate_month">
|
||||
# Invalid dates still render the failed date:
|
||||
self.assertEqual(w.render('mydate', '2010-02-31'), """<select name="mydate_month" id="id_mydate_month">
|
||||
<option value="1">January</option>
|
||||
<option value="2" selected="selected">February</option>
|
||||
<option value="3">March</option>
|
||||
|
@ -209,13 +209,11 @@ Invalid dates still render the failed date:
|
|||
<option value="2014">2014</option>
|
||||
<option value="2015">2015</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>
|
||||
</select>""")
|
||||
|
||||
Using a SelectDateWidget in a form:
|
||||
|
||||
>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), required=False)
|
||||
>>> print w.render('mydate', '')
|
||||
<select name="mydate_month" id="id_mydate_month">
|
||||
# Using a SelectDateWidget in a form:
|
||||
w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), required=False)
|
||||
self.assertEqual(w.render('mydate', ''), """<select name="mydate_month" id="id_mydate_month">
|
||||
<option value="0">---</option>
|
||||
<option value="1">January</option>
|
||||
<option value="2">February</option>
|
||||
|
@ -276,9 +274,8 @@ Using a SelectDateWidget in a form:
|
|||
<option value="2014">2014</option>
|
||||
<option value="2015">2015</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>
|
||||
>>> print w.render('mydate', '2010-04-15')
|
||||
<select name="mydate_month" id="id_mydate_month">
|
||||
</select>""")
|
||||
self.assertEqual(w.render('mydate', '2010-04-15'), """<select name="mydate_month" id="id_mydate_month">
|
||||
<option value="0">---</option>
|
||||
<option value="1">January</option>
|
||||
<option value="2">February</option>
|
||||
|
@ -339,40 +336,213 @@ Using a SelectDateWidget in a form:
|
|||
<option value="2014">2014</option>
|
||||
<option value="2015">2015</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>""")
|
||||
|
||||
class GetDate(Form):
|
||||
mydate = DateField(widget=SelectDateWidget)
|
||||
|
||||
a = GetDate({'mydate_month':'4', 'mydate_day':'1', 'mydate_year':'2008'})
|
||||
self.assertTrue(a.is_valid())
|
||||
self.assertEqual(a.cleaned_data['mydate'], datetime.date(2008, 4, 1))
|
||||
|
||||
# As with any widget that implements get_value_from_datadict,
|
||||
# we must be prepared to accept the input from the "as_hidden"
|
||||
# rendering as well.
|
||||
|
||||
self.assertEqual(a['mydate'].as_hidden(), '<input type="hidden" name="mydate" value="2008-4-1" id="id_mydate" />')
|
||||
|
||||
b = GetDate({'mydate':'2008-4-1'})
|
||||
self.assertTrue(b.is_valid())
|
||||
self.assertEqual(b.cleaned_data['mydate'], datetime.date(2008, 4, 1))
|
||||
|
||||
def test_multiwidget(self):
|
||||
# MultiWidget and MultiValueField #############################################
|
||||
# MultiWidgets are widgets composed of other widgets. They are usually
|
||||
# combined with MultiValueFields - a field that is composed of other fields.
|
||||
# MulitWidgets can themselved be composed of other MultiWidgets.
|
||||
# SplitDateTimeWidget is one example of a MultiWidget.
|
||||
|
||||
class ComplexMultiWidget(MultiWidget):
|
||||
def __init__(self, attrs=None):
|
||||
widgets = (
|
||||
TextInput(),
|
||||
SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
|
||||
SplitDateTimeWidget(),
|
||||
)
|
||||
super(ComplexMultiWidget, self).__init__(widgets, attrs)
|
||||
|
||||
def decompress(self, value):
|
||||
if value:
|
||||
data = value.split(',')
|
||||
return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])]
|
||||
return [None, None, None]
|
||||
|
||||
def format_output(self, rendered_widgets):
|
||||
return u'\n'.join(rendered_widgets)
|
||||
|
||||
w = ComplexMultiWidget()
|
||||
self.assertEqual(w.render('name', 'some text,JP,2007-04-25 06:24:00'), """<input type="text" name="name_0" value="some text" />
|
||||
<select multiple="multiple" name="name_1">
|
||||
<option value="J" selected="selected">John</option>
|
||||
<option value="P" selected="selected">Paul</option>
|
||||
<option value="G">George</option>
|
||||
<option value="R">Ringo</option>
|
||||
</select>
|
||||
>>> class GetDate(Form):
|
||||
... mydate = DateField(widget=SelectDateWidget)
|
||||
>>> a = GetDate({'mydate_month':'4', 'mydate_day':'1', 'mydate_year':'2008'})
|
||||
>>> print a.is_valid()
|
||||
True
|
||||
>>> print a.cleaned_data['mydate']
|
||||
2008-04-01
|
||||
<input type="text" name="name_2_0" value="2007-04-25" /><input type="text" name="name_2_1" value="06:24:00" />""")
|
||||
|
||||
As with any widget that implements get_value_from_datadict,
|
||||
we must be prepared to accept the input from the "as_hidden"
|
||||
rendering as well.
|
||||
class ComplexField(MultiValueField):
|
||||
def __init__(self, required=True, widget=None, label=None, initial=None):
|
||||
fields = (
|
||||
CharField(),
|
||||
MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
|
||||
SplitDateTimeField()
|
||||
)
|
||||
super(ComplexField, self).__init__(fields, required, widget, label, initial)
|
||||
|
||||
>>> print a['mydate'].as_hidden()
|
||||
<input type="hidden" name="mydate" value="2008-4-1" id="id_mydate" />
|
||||
>>> b=GetDate({'mydate':'2008-4-1'})
|
||||
>>> print b.is_valid()
|
||||
True
|
||||
>>> print b.cleaned_data['mydate']
|
||||
2008-04-01
|
||||
def compress(self, data_list):
|
||||
if data_list:
|
||||
return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
|
||||
return None
|
||||
|
||||
f = ComplexField(widget=w)
|
||||
self.assertEqual(f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']]), u'some text,JP,2007-04-25 06:24:00')
|
||||
self.assertFormErrors([u'Select a valid choice. X is not one of the available choices.'], f.clean, ['some text',['X'], ['2007-04-25','6:24:00']])
|
||||
|
||||
# If insufficient data is provided, None is substituted
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, ['some text',['JP']])
|
||||
|
||||
class ComplexFieldForm(Form):
|
||||
field1 = ComplexField(widget=w)
|
||||
|
||||
f = ComplexFieldForm()
|
||||
self.assertEqual(f.as_table(), """<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" />
|
||||
<select multiple="multiple" name="field1_1" id="id_field1_1">
|
||||
<option value="J">John</option>
|
||||
<option value="P">Paul</option>
|
||||
<option value="G">George</option>
|
||||
<option value="R">Ringo</option>
|
||||
</select>
|
||||
<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr>""")
|
||||
|
||||
f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
|
||||
self.assertEqual(f.as_table(), """<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" />
|
||||
<select multiple="multiple" name="field1_1" id="id_field1_1">
|
||||
<option value="J" selected="selected">John</option>
|
||||
<option value="P" selected="selected">Paul</option>
|
||||
<option value="G">George</option>
|
||||
<option value="R">Ringo</option>
|
||||
</select>
|
||||
<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>""")
|
||||
|
||||
self.assertEqual(f.cleaned_data['field1'], u'some text,JP,2007-04-25 06:24:00')
|
||||
|
||||
def test_ipaddress(self):
|
||||
f = IPAddressField()
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, '')
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, None)
|
||||
self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
|
||||
|
||||
f = IPAddressField(required=False)
|
||||
self.assertEqual(f.clean(''), u'')
|
||||
self.assertEqual(f.clean(None), u'')
|
||||
self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
|
||||
|
||||
def test_smart_unicode(self):
|
||||
class Test:
|
||||
def __str__(self):
|
||||
return 'ŠĐĆŽćžšđ'
|
||||
|
||||
class TestU:
|
||||
def __str__(self):
|
||||
return 'Foo'
|
||||
def __unicode__(self):
|
||||
return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
|
||||
|
||||
self.assertEqual(smart_unicode(Test()), u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
|
||||
self.assertEqual(smart_unicode(TestU()), u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
|
||||
self.assertEqual(smart_unicode(1), u'1')
|
||||
self.assertEqual(smart_unicode('foo'), u'foo')
|
||||
|
||||
def test_accessing_clean(self):
|
||||
class UserForm(Form):
|
||||
username = CharField(max_length=10)
|
||||
password = CharField(widget=PasswordInput)
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
|
||||
if not self.errors:
|
||||
data['username'] = data['username'].lower()
|
||||
|
||||
return data
|
||||
|
||||
f = UserForm({'username': 'SirRobin', 'password': 'blue'})
|
||||
self.assertTrue(f.is_valid())
|
||||
self.assertEqual(f.cleaned_data['username'], u'sirrobin')
|
||||
|
||||
def test_overriding_errorlist(self):
|
||||
class DivErrorList(ErrorList):
|
||||
def __unicode__(self):
|
||||
return self.as_divs()
|
||||
|
||||
def as_divs(self):
|
||||
if not self: return u''
|
||||
return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % force_unicode(e) for e in self])
|
||||
|
||||
class CommentForm(Form):
|
||||
name = CharField(max_length=50, required=False)
|
||||
email = EmailField()
|
||||
comment = CharField()
|
||||
|
||||
data = dict(email='invalid')
|
||||
f = CommentForm(data, auto_id=False, error_class=DivErrorList)
|
||||
self.assertEqual(f.as_p(), """<p>Name: <input type="text" name="name" maxlength="50" /></p>
|
||||
<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
|
||||
<p>Email: <input type="text" name="email" value="invalid" /></p>
|
||||
<div class="errorlist"><div class="error">This field is required.</div></div>
|
||||
<p>Comment: <input type="text" name="comment" /></p>""")
|
||||
|
||||
def test_multipart_encoded_form(self):
|
||||
class FormWithoutFile(Form):
|
||||
username = CharField()
|
||||
|
||||
class FormWithFile(Form):
|
||||
username = CharField()
|
||||
file = FileField()
|
||||
|
||||
class FormWithImage(Form):
|
||||
image = ImageField()
|
||||
|
||||
self.assertFalse(FormWithoutFile().is_multipart())
|
||||
self.assertTrue(FormWithFile().is_multipart())
|
||||
self.assertTrue(FormWithImage().is_multipart())
|
||||
|
||||
|
||||
USE_L10N tests
|
||||
class FormsExtraL10NTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(FormsExtraL10NTestCase, self).setUp()
|
||||
self.old_use_l10n = getattr(settings, 'USE_L10N', False)
|
||||
settings.USE_L10N = True
|
||||
translation.activate('nl')
|
||||
|
||||
>>> from django.utils import translation
|
||||
>>> translation.activate('nl')
|
||||
>>> from django.conf import settings
|
||||
>>> settings.USE_L10N=True
|
||||
def tearDown(self):
|
||||
translation.deactivate()
|
||||
settings.USE_L10N = self.old_use_l10n
|
||||
super(FormsExtraL10NTestCase, self).tearDown()
|
||||
|
||||
>>> w.value_from_datadict({'date_year': '2010', 'date_month': '8', 'date_day': '13'}, {}, 'date')
|
||||
'13-08-2010'
|
||||
def test_l10n(self):
|
||||
w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), required=False)
|
||||
self.assertEqual(w.value_from_datadict({'date_year': '2010', 'date_month': '8', 'date_day': '13'}, {}, 'date'), '13-08-2010')
|
||||
|
||||
>>> print w.render('date', '13-08-2010')
|
||||
<select name="date_day" id="id_date_day">
|
||||
self.assertEqual(w.render('date', '13-08-2010'), """<select name="date_day" id="id_date_day">
|
||||
<option value="0">---</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
|
@ -433,242 +603,8 @@ USE_L10N tests
|
|||
<option value="2014">2014</option>
|
||||
<option value="2015">2015</option>
|
||||
<option value="2016">2016</option>
|
||||
</select>
|
||||
</select>""")
|
||||
|
||||
Years before 1900 work
|
||||
>>> w = SelectDateWidget(years=('1899',))
|
||||
>>> w.value_from_datadict({'date_year': '1899', 'date_month': '8', 'date_day': '13'}, {}, 'date')
|
||||
'13-08-1899'
|
||||
|
||||
>>> translation.deactivate()
|
||||
|
||||
# MultiWidget and MultiValueField #############################################
|
||||
# MultiWidgets are widgets composed of other widgets. They are usually
|
||||
# combined with MultiValueFields - a field that is composed of other fields.
|
||||
# MulitWidgets can themselved be composed of other MultiWidgets.
|
||||
# SplitDateTimeWidget is one example of a MultiWidget.
|
||||
|
||||
>>> class ComplexMultiWidget(MultiWidget):
|
||||
... def __init__(self, attrs=None):
|
||||
... widgets = (
|
||||
... TextInput(),
|
||||
... SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
|
||||
... SplitDateTimeWidget(),
|
||||
... )
|
||||
... super(ComplexMultiWidget, self).__init__(widgets, attrs)
|
||||
...
|
||||
... def decompress(self, value):
|
||||
... if value:
|
||||
... data = value.split(',')
|
||||
... return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])]
|
||||
... return [None, None, None]
|
||||
... def format_output(self, rendered_widgets):
|
||||
... return u'\n'.join(rendered_widgets)
|
||||
>>> w = ComplexMultiWidget()
|
||||
>>> print w.render('name', 'some text,JP,2007-04-25 06:24:00')
|
||||
<input type="text" name="name_0" value="some text" />
|
||||
<select multiple="multiple" name="name_1">
|
||||
<option value="J" selected="selected">John</option>
|
||||
<option value="P" selected="selected">Paul</option>
|
||||
<option value="G">George</option>
|
||||
<option value="R">Ringo</option>
|
||||
</select>
|
||||
<input type="text" name="name_2_0" value="2007-04-25" /><input type="text" name="name_2_1" value="06:24:00" />
|
||||
|
||||
>>> class ComplexField(MultiValueField):
|
||||
... def __init__(self, required=True, widget=None, label=None, initial=None):
|
||||
... fields = (
|
||||
... CharField(),
|
||||
... MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
|
||||
... SplitDateTimeField()
|
||||
... )
|
||||
... super(ComplexField, self).__init__(fields, required, widget, label, initial)
|
||||
...
|
||||
... def compress(self, data_list):
|
||||
... if data_list:
|
||||
... return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
|
||||
... return None
|
||||
|
||||
>>> f = ComplexField(widget=w)
|
||||
>>> f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']])
|
||||
u'some text,JP,2007-04-25 06:24:00'
|
||||
>>> f.clean(['some text',['X'], ['2007-04-25','6:24:00']])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Select a valid choice. X is not one of the available choices.']
|
||||
|
||||
# If insufficient data is provided, None is substituted
|
||||
>>> f.clean(['some text',['JP']])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'This field is required.']
|
||||
|
||||
>>> class ComplexFieldForm(Form):
|
||||
... field1 = ComplexField(widget=w)
|
||||
>>> f = ComplexFieldForm()
|
||||
>>> print f
|
||||
<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" />
|
||||
<select multiple="multiple" name="field1_1" id="id_field1_1">
|
||||
<option value="J">John</option>
|
||||
<option value="P">Paul</option>
|
||||
<option value="G">George</option>
|
||||
<option value="R">Ringo</option>
|
||||
</select>
|
||||
<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr>
|
||||
|
||||
>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
|
||||
>>> print f
|
||||
<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" />
|
||||
<select multiple="multiple" name="field1_1" id="id_field1_1">
|
||||
<option value="J" selected="selected">John</option>
|
||||
<option value="P" selected="selected">Paul</option>
|
||||
<option value="G">George</option>
|
||||
<option value="R">Ringo</option>
|
||||
</select>
|
||||
<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>
|
||||
|
||||
>>> f.cleaned_data['field1']
|
||||
u'some text,JP,2007-04-25 06:24:00'
|
||||
|
||||
|
||||
# IPAddressField ##################################################################
|
||||
|
||||
>>> f = IPAddressField()
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'This field is required.']
|
||||
>>> f.clean(None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'This field is required.']
|
||||
>>> f.clean('127.0.0.1')
|
||||
u'127.0.0.1'
|
||||
>>> f.clean('foo')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
>>> f.clean('127.0.0.')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
>>> f.clean('1.2.3.4.5')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
>>> f.clean('256.125.1.5')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
|
||||
>>> f = IPAddressField(required=False)
|
||||
>>> f.clean('')
|
||||
u''
|
||||
>>> f.clean(None)
|
||||
u''
|
||||
>>> f.clean('127.0.0.1')
|
||||
u'127.0.0.1'
|
||||
>>> f.clean('foo')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
>>> f.clean('127.0.0.')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
>>> f.clean('1.2.3.4.5')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
>>> f.clean('256.125.1.5')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid IPv4 address.']
|
||||
|
||||
#################################
|
||||
# Tests of underlying functions #
|
||||
#################################
|
||||
|
||||
# smart_unicode tests
|
||||
>>> from django.utils.encoding import smart_unicode
|
||||
>>> class Test:
|
||||
... def __str__(self):
|
||||
... return 'ŠĐĆŽćžšđ'
|
||||
>>> class TestU:
|
||||
... def __str__(self):
|
||||
... return 'Foo'
|
||||
... def __unicode__(self):
|
||||
... return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
|
||||
>>> smart_unicode(Test())
|
||||
u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
|
||||
>>> smart_unicode(TestU())
|
||||
u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
|
||||
>>> smart_unicode(1)
|
||||
u'1'
|
||||
>>> smart_unicode('foo')
|
||||
u'foo'
|
||||
|
||||
|
||||
####################################
|
||||
# Test accessing errors in clean() #
|
||||
####################################
|
||||
|
||||
>>> class UserForm(Form):
|
||||
... username = CharField(max_length=10)
|
||||
... password = CharField(widget=PasswordInput)
|
||||
... def clean(self):
|
||||
... data = self.cleaned_data
|
||||
... if not self.errors:
|
||||
... data['username'] = data['username'].lower()
|
||||
... return data
|
||||
|
||||
>>> f = UserForm({'username': 'SirRobin', 'password': 'blue'})
|
||||
>>> f.is_valid()
|
||||
True
|
||||
>>> f.cleaned_data['username']
|
||||
u'sirrobin'
|
||||
|
||||
#######################################
|
||||
# Test overriding ErrorList in a form #
|
||||
#######################################
|
||||
|
||||
>>> from django.forms.util import ErrorList
|
||||
>>> class DivErrorList(ErrorList):
|
||||
... def __unicode__(self):
|
||||
... return self.as_divs()
|
||||
... def as_divs(self):
|
||||
... if not self: return u''
|
||||
... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % force_unicode(e) for e in self])
|
||||
>>> class CommentForm(Form):
|
||||
... name = CharField(max_length=50, required=False)
|
||||
... email = EmailField()
|
||||
... comment = CharField()
|
||||
>>> data = dict(email='invalid')
|
||||
>>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
|
||||
>>> print f.as_p()
|
||||
<p>Name: <input type="text" name="name" maxlength="50" /></p>
|
||||
<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
|
||||
<p>Email: <input type="text" name="email" value="invalid" /></p>
|
||||
<div class="errorlist"><div class="error">This field is required.</div></div>
|
||||
<p>Comment: <input type="text" name="comment" /></p>
|
||||
|
||||
#################################
|
||||
# Test multipart-encoded form #
|
||||
#################################
|
||||
|
||||
>>> class FormWithoutFile(Form):
|
||||
... username = CharField()
|
||||
>>> class FormWithFile(Form):
|
||||
... username = CharField()
|
||||
... file = FileField()
|
||||
>>> class FormWithImage(Form):
|
||||
... image = ImageField()
|
||||
|
||||
>>> FormWithoutFile().is_multipart()
|
||||
False
|
||||
>>> FormWithFile().is_multipart()
|
||||
True
|
||||
>>> FormWithImage().is_multipart()
|
||||
True
|
||||
|
||||
"""
|
||||
# Years before 1900 work
|
||||
w = SelectDateWidget(years=('1899',))
|
||||
self.assertEqual(w.value_from_datadict({'date_year': '1899', 'date_month': '8', 'date_day': '13'}, {}, 'date'), '13-08-1899')
|
|
@ -57,12 +57,12 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(message, str(e))
|
||||
|
||||
def test_field_sets_widget_is_required(self):
|
||||
self.assertEqual(Field(required=True).widget.is_required, True)
|
||||
self.assertEqual(Field(required=False).widget.is_required, False)
|
||||
self.assertTrue(Field(required=True).widget.is_required)
|
||||
self.assertFalse(Field(required=False).widget.is_required)
|
||||
|
||||
# CharField ###################################################################
|
||||
|
||||
def test_charfield_0(self):
|
||||
def test_charfield_1(self):
|
||||
f = CharField()
|
||||
self.assertEqual(u'1', f.clean(1))
|
||||
self.assertEqual(u'hello', f.clean('hello'))
|
||||
|
@ -70,7 +70,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3]))
|
||||
|
||||
def test_charfield_1(self):
|
||||
def test_charfield_2(self):
|
||||
f = CharField(required=False)
|
||||
self.assertEqual(u'1', f.clean(1))
|
||||
self.assertEqual(u'hello', f.clean('hello'))
|
||||
|
@ -78,20 +78,20 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3]))
|
||||
|
||||
def test_charfield_2(self):
|
||||
def test_charfield_3(self):
|
||||
f = CharField(max_length=10, required=False)
|
||||
self.assertEqual(u'12345', f.clean('12345'))
|
||||
self.assertEqual(u'1234567890', f.clean('1234567890'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '1234567890a')
|
||||
|
||||
def test_charfield_3(self):
|
||||
def test_charfield_4(self):
|
||||
f = CharField(min_length=10, required=False)
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345')
|
||||
self.assertEqual(u'1234567890', f.clean('1234567890'))
|
||||
self.assertEqual(u'1234567890a', f.clean('1234567890a'))
|
||||
|
||||
def test_charfield_4(self):
|
||||
def test_charfield_5(self):
|
||||
f = CharField(min_length=10, required=True)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345')
|
||||
|
@ -100,7 +100,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# IntegerField ################################################################
|
||||
|
||||
def test_integerfield_5(self):
|
||||
def test_integerfield_1(self):
|
||||
f = IntegerField()
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -115,7 +115,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(1, f.clean(' 1 '))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a')
|
||||
|
||||
def test_integerfield_6(self):
|
||||
def test_integerfield_2(self):
|
||||
f = IntegerField(required=False)
|
||||
self.assertEqual(None, f.clean(''))
|
||||
self.assertEqual('None', repr(f.clean('')))
|
||||
|
@ -130,7 +130,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(1, f.clean(' 1 '))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a')
|
||||
|
||||
def test_integerfield_7(self):
|
||||
def test_integerfield_3(self):
|
||||
f = IntegerField(max_value=10)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
self.assertEqual(1, f.clean(1))
|
||||
|
@ -139,7 +139,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(10, f.clean('10'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, '11')
|
||||
|
||||
def test_integerfield_8(self):
|
||||
def test_integerfield_4(self):
|
||||
f = IntegerField(min_value=10)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1)
|
||||
|
@ -148,7 +148,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(10, f.clean('10'))
|
||||
self.assertEqual(11, f.clean('11'))
|
||||
|
||||
def test_integerfield_9(self):
|
||||
def test_integerfield_5(self):
|
||||
f = IntegerField(min_value=10, max_value=20)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1)
|
||||
|
@ -161,7 +161,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# FloatField ##################################################################
|
||||
|
||||
def test_floatfield_10(self):
|
||||
def test_floatfield_1(self):
|
||||
f = FloatField()
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -177,13 +177,13 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(1.0, f.clean(' 1.0 '))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a')
|
||||
|
||||
def test_floatfield_11(self):
|
||||
def test_floatfield_2(self):
|
||||
f = FloatField(required=False)
|
||||
self.assertEqual(None, f.clean(''))
|
||||
self.assertEqual(None, f.clean(None))
|
||||
self.assertEqual(1.0, f.clean('1'))
|
||||
|
||||
def test_floatfield_12(self):
|
||||
def test_floatfield_3(self):
|
||||
f = FloatField(max_value=1.5, min_value=0.5)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4')
|
||||
|
@ -192,7 +192,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# DecimalField ################################################################
|
||||
|
||||
def test_decimalfield_13(self):
|
||||
def test_decimalfield_1(self):
|
||||
f = DecimalField(max_digits=4, decimal_places=2)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -223,13 +223,13 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-000.12345')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '--0.12')
|
||||
|
||||
def test_decimalfield_14(self):
|
||||
def test_decimalfield_2(self):
|
||||
f = DecimalField(max_digits=4, decimal_places=2, required=False)
|
||||
self.assertEqual(None, f.clean(''))
|
||||
self.assertEqual(None, f.clean(None))
|
||||
self.assertEqual(f.clean('1'), Decimal("1"))
|
||||
|
||||
def test_decimalfield_15(self):
|
||||
def test_decimalfield_3(self):
|
||||
f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4')
|
||||
|
@ -238,11 +238,11 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(f.clean('.5'), Decimal("0.5"))
|
||||
self.assertEqual(f.clean('00.50'), Decimal("0.50"))
|
||||
|
||||
def test_decimalfield_16(self):
|
||||
def test_decimalfield_4(self):
|
||||
f = DecimalField(decimal_places=2)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '0.00000001')
|
||||
|
||||
def test_decimalfield_17(self):
|
||||
def test_decimalfield_5(self):
|
||||
f = DecimalField(max_digits=3)
|
||||
# Leading whole zeros "collapse" to one digit.
|
||||
self.assertEqual(f.clean('0000000.10'), Decimal("0.1"))
|
||||
|
@ -253,14 +253,14 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 3 digits in total.']", f.clean, '000000.0002')
|
||||
self.assertEqual(f.clean('.002'), Decimal("0.002"))
|
||||
|
||||
def test_decimalfield_18(self):
|
||||
def test_decimalfield_6(self):
|
||||
f = DecimalField(max_digits=2, decimal_places=2)
|
||||
self.assertEqual(f.clean('.01'), Decimal(".01"))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 0 digits before the decimal point.']", f.clean, '1.1')
|
||||
|
||||
# DateField ###################################################################
|
||||
|
||||
def test_datefield_19(self):
|
||||
def test_datefield_1(self):
|
||||
f = DateField()
|
||||
self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25)))
|
||||
self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
|
||||
|
@ -279,14 +279,14 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '25/10/06')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
||||
def test_datefield_20(self):
|
||||
def test_datefield_2(self):
|
||||
f = DateField(required=False)
|
||||
self.assertEqual(None, f.clean(None))
|
||||
self.assertEqual('None', repr(f.clean(None)))
|
||||
self.assertEqual(None, f.clean(''))
|
||||
self.assertEqual('None', repr(f.clean('')))
|
||||
|
||||
def test_datefield_21(self):
|
||||
def test_datefield_3(self):
|
||||
f = DateField(input_formats=['%Y %m %d'])
|
||||
self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25)))
|
||||
self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
|
||||
|
@ -297,7 +297,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# TimeField ###################################################################
|
||||
|
||||
def test_timefield_22(self):
|
||||
def test_timefield_1(self):
|
||||
f = TimeField()
|
||||
self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25)))
|
||||
self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59)))
|
||||
|
@ -306,7 +306,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, 'hello')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '1:24 p.m.')
|
||||
|
||||
def test_timefield_23(self):
|
||||
def test_timefield_2(self):
|
||||
f = TimeField(input_formats=['%I:%M %p'])
|
||||
self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25)))
|
||||
self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59)))
|
||||
|
@ -316,7 +316,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# DateTimeField ###############################################################
|
||||
|
||||
def test_datetimefield_24(self):
|
||||
def test_datetimefield_1(self):
|
||||
f = DateTimeField()
|
||||
self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25)))
|
||||
self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
|
||||
|
@ -337,7 +337,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, 'hello')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 4:30 p.m.')
|
||||
|
||||
def test_datetimefield_25(self):
|
||||
def test_datetimefield_2(self):
|
||||
f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
|
||||
self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25)))
|
||||
self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
|
||||
|
@ -346,7 +346,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006 10 25 2:30 PM'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 14:30:45')
|
||||
|
||||
def test_datetimefield_26(self):
|
||||
def test_datetimefield_3(self):
|
||||
f = DateTimeField(required=False)
|
||||
self.assertEqual(None, f.clean(None))
|
||||
self.assertEqual('None', repr(f.clean(None)))
|
||||
|
@ -355,7 +355,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# RegexField ##################################################################
|
||||
|
||||
def test_regexfield_27(self):
|
||||
def test_regexfield_1(self):
|
||||
f = RegexField('^\d[A-F]\d$')
|
||||
self.assertEqual(u'2A2', f.clean('2A2'))
|
||||
self.assertEqual(u'3F3', f.clean('3F3'))
|
||||
|
@ -364,14 +364,14 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
|
||||
def test_regexfield_28(self):
|
||||
def test_regexfield_2(self):
|
||||
f = RegexField('^\d[A-F]\d$', required=False)
|
||||
self.assertEqual(u'2A2', f.clean('2A2'))
|
||||
self.assertEqual(u'3F3', f.clean('3F3'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
|
||||
def test_regexfield_29(self):
|
||||
def test_regexfield_3(self):
|
||||
f = RegexField(re.compile('^\d[A-F]\d$'))
|
||||
self.assertEqual(u'2A2', f.clean('2A2'))
|
||||
self.assertEqual(u'3F3', f.clean('3F3'))
|
||||
|
@ -379,13 +379,13 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ')
|
||||
|
||||
def test_regexfield_30(self):
|
||||
def test_regexfield_4(self):
|
||||
f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.')
|
||||
self.assertEqual(u'1234', f.clean('1234'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, '123')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, 'abcd')
|
||||
|
||||
def test_regexfield_31(self):
|
||||
def test_regexfield_5(self):
|
||||
f = RegexField('^\d+$', min_length=5, max_length=10)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, '123')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).', u'Enter a valid value.']", f.clean, 'abc')
|
||||
|
@ -396,7 +396,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# EmailField ##################################################################
|
||||
|
||||
def test_emailfield_32(self):
|
||||
def test_emailfield_1(self):
|
||||
f = EmailField()
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -424,7 +424,7 @@ class FieldsTests(TestCase):
|
|||
'viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058'
|
||||
)
|
||||
|
||||
def test_emailfield_33(self):
|
||||
def test_emailfield_2(self):
|
||||
f = EmailField(required=False)
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertEqual(u'', f.clean(None))
|
||||
|
@ -434,7 +434,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar')
|
||||
|
||||
def test_emailfield_34(self):
|
||||
def test_emailfield_3(self):
|
||||
f = EmailField(min_length=10, max_length=15)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 9).']", f.clean, 'a@foo.com')
|
||||
self.assertEqual(u'alf@foo.com', f.clean('alf@foo.com'))
|
||||
|
@ -442,7 +442,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# FileField ##################################################################
|
||||
|
||||
def test_filefield_35(self):
|
||||
def test_filefield_1(self):
|
||||
f = FileField()
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '', '')
|
||||
|
@ -460,7 +460,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'))))
|
||||
self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf')))
|
||||
|
||||
def test_filefield_36(self):
|
||||
def test_filefield_2(self):
|
||||
f = FileField(max_length = 5)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this filename has at most 5 characters (it has 18).']", f.clean, SimpleUploadedFile('test_maxlength.txt', 'hello world'))
|
||||
self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf'))
|
||||
|
@ -469,7 +469,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# URLField ##################################################################
|
||||
|
||||
def test_urlfield_37(self):
|
||||
def test_urlfield_1(self):
|
||||
f = URLField()
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -505,7 +505,7 @@ class FieldsTests(TestCase):
|
|||
# domains that don't fail the domain label length check in the regex
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*60,))
|
||||
|
||||
def test_urlfield_38(self):
|
||||
def test_urlfield_2(self):
|
||||
f = URLField(required=False)
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertEqual(u'', f.clean(None))
|
||||
|
@ -517,7 +517,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com')
|
||||
|
||||
def test_urlfield_39(self):
|
||||
def test_urlfield_3(self):
|
||||
f = URLField(verify_exists=True)
|
||||
self.assertEqual(u'http://www.google.com/', f.clean('http://www.google.com')) # This will fail if there's no Internet connection
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
|
||||
|
@ -539,24 +539,24 @@ class FieldsTests(TestCase):
|
|||
except ValidationError, e:
|
||||
self.assertEqual("[u'This URL appears to be a broken link.']", str(e))
|
||||
|
||||
def test_urlfield_40(self):
|
||||
def test_urlfield_4(self):
|
||||
f = URLField(verify_exists=True, required=False)
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertEqual(u'http://www.google.com/', f.clean('http://www.google.com')) # This will fail if there's no Internet connection
|
||||
|
||||
def test_urlfield_41(self):
|
||||
def test_urlfield_5(self):
|
||||
f = URLField(min_length=15, max_length=20)
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 15 characters (it has 13).']", f.clean, 'http://f.com')
|
||||
self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 38).']", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com')
|
||||
|
||||
def test_urlfield_42(self):
|
||||
def test_urlfield_6(self):
|
||||
f = URLField(required=False)
|
||||
self.assertEqual(u'http://example.com/', f.clean('example.com'))
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertEqual(u'https://example.com/', f.clean('https://example.com'))
|
||||
|
||||
def test_urlfield_43(self):
|
||||
def test_urlfield_7(self):
|
||||
f = URLField()
|
||||
self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
|
||||
self.assertEqual(u'http://example.com/test', f.clean('http://example.com/test'))
|
||||
|
@ -567,7 +567,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# BooleanField ################################################################
|
||||
|
||||
def test_booleanfield_44(self):
|
||||
def test_booleanfield_1(self):
|
||||
f = BooleanField()
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -579,7 +579,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(True, f.clean('True'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, 'False')
|
||||
|
||||
def test_booleanfield_45(self):
|
||||
def test_booleanfield_2(self):
|
||||
f = BooleanField(required=False)
|
||||
self.assertEqual(False, f.clean(''))
|
||||
self.assertEqual(False, f.clean(None))
|
||||
|
@ -594,7 +594,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# ChoiceField #################################################################
|
||||
|
||||
def test_choicefield_46(self):
|
||||
def test_choicefield_1(self):
|
||||
f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -602,7 +602,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(u'1', f.clean('1'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3')
|
||||
|
||||
def test_choicefield_47(self):
|
||||
def test_choicefield_2(self):
|
||||
f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
|
||||
self.assertEqual(u'', f.clean(''))
|
||||
self.assertEqual(u'', f.clean(None))
|
||||
|
@ -610,12 +610,12 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(u'1', f.clean('1'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3')
|
||||
|
||||
def test_choicefield_48(self):
|
||||
def test_choicefield_3(self):
|
||||
f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
|
||||
self.assertEqual(u'J', f.clean('J'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. John is not one of the available choices.']", f.clean, 'John')
|
||||
|
||||
def test_choicefield_49(self):
|
||||
def test_choicefield_4(self):
|
||||
f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
|
||||
self.assertEqual(u'1', f.clean(1))
|
||||
self.assertEqual(u'1', f.clean('1'))
|
||||
|
@ -629,22 +629,22 @@ class FieldsTests(TestCase):
|
|||
# TypedChoiceField is just like ChoiceField, except that coerced types will
|
||||
# be returned:
|
||||
|
||||
def test_typedchoicefield_50(self):
|
||||
def test_typedchoicefield_1(self):
|
||||
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
|
||||
self.assertEqual(1, f.clean('1'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, '2')
|
||||
|
||||
def test_typedchoicefield_51(self):
|
||||
def test_typedchoicefield_2(self):
|
||||
# Different coercion, same validation.
|
||||
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float)
|
||||
self.assertEqual(1.0, f.clean('1'))
|
||||
|
||||
def test_typedchoicefield_52(self):
|
||||
def test_typedchoicefield_3(self):
|
||||
# This can also cause weirdness: be careful (bool(-1) == True, remember)
|
||||
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool)
|
||||
self.assertEqual(True, f.clean('-1'))
|
||||
|
||||
def test_typedchoicefield_53(self):
|
||||
def test_typedchoicefield_4(self):
|
||||
# Even more weirdness: if you have a valid choice but your coercion function
|
||||
# can't coerce, you'll still get a validation error. Don't do this!
|
||||
f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
|
||||
|
@ -652,19 +652,19 @@ class FieldsTests(TestCase):
|
|||
# Required fields require values
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
|
||||
def test_typedchoicefield_54(self):
|
||||
def test_typedchoicefield_5(self):
|
||||
# Non-required fields aren't required
|
||||
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
|
||||
self.assertEqual('', f.clean(''))
|
||||
# If you want cleaning an empty value to return a different type, tell the field
|
||||
|
||||
def test_typedchoicefield_55(self):
|
||||
def test_typedchoicefield_6(self):
|
||||
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
|
||||
self.assertEqual(None, f.clean(''))
|
||||
|
||||
# NullBooleanField ############################################################
|
||||
|
||||
def test_nullbooleanfield_56(self):
|
||||
def test_nullbooleanfield_1(self):
|
||||
f = NullBooleanField()
|
||||
self.assertEqual(None, f.clean(''))
|
||||
self.assertEqual(True, f.clean(True))
|
||||
|
@ -677,7 +677,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(None, f.clean('hello'))
|
||||
|
||||
|
||||
def test_nullbooleanfield_57(self):
|
||||
def test_nullbooleanfield_2(self):
|
||||
# Make sure that the internal value is preserved if using HiddenInput (#7753)
|
||||
class HiddenNullBooleanForm(Form):
|
||||
hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
|
||||
|
@ -685,7 +685,7 @@ class FieldsTests(TestCase):
|
|||
f = HiddenNullBooleanForm()
|
||||
self.assertEqual('<input type="hidden" name="hidden_nullbool1" value="True" id="id_hidden_nullbool1" /><input type="hidden" name="hidden_nullbool2" value="False" id="id_hidden_nullbool2" />', str(f))
|
||||
|
||||
def test_nullbooleanfield_58(self):
|
||||
def test_nullbooleanfield_3(self):
|
||||
class HiddenNullBooleanForm(Form):
|
||||
hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
|
||||
hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
|
||||
|
@ -694,7 +694,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(True, f.cleaned_data['hidden_nullbool1'])
|
||||
self.assertEqual(False, f.cleaned_data['hidden_nullbool2'])
|
||||
|
||||
def test_nullbooleanfield_59(self):
|
||||
def test_nullbooleanfield_4(self):
|
||||
# Make sure we're compatible with MySQL, which uses 0 and 1 for its boolean
|
||||
# values. (#9609)
|
||||
NULLBOOL_CHOICES = (('1', 'Yes'), ('0', 'No'), ('', 'Unknown'))
|
||||
|
@ -710,7 +710,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# MultipleChoiceField #########################################################
|
||||
|
||||
def test_multiplechoicefield_60(self):
|
||||
def test_multiplechoicefield_1(self):
|
||||
f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
@ -724,7 +724,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, ())
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3'])
|
||||
|
||||
def test_multiplechoicefield_61(self):
|
||||
def test_multiplechoicefield_2(self):
|
||||
f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
|
||||
self.assertEqual([], f.clean(''))
|
||||
self.assertEqual([], f.clean(None))
|
||||
|
@ -738,7 +738,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual([], f.clean(()))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3'])
|
||||
|
||||
def test_multiplechoicefield_62(self):
|
||||
def test_multiplechoicefield_3(self):
|
||||
f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
|
||||
self.assertEqual([u'1'], f.clean([1]))
|
||||
self.assertEqual([u'1'], f.clean(['1']))
|
||||
|
@ -751,7 +751,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# ComboField ##################################################################
|
||||
|
||||
def test_combofield_63(self):
|
||||
def test_combofield_1(self):
|
||||
f = ComboField(fields=[CharField(max_length=20), EmailField()])
|
||||
self.assertEqual(u'test@example.com', f.clean('test@example.com'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com')
|
||||
|
@ -759,7 +759,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
|
||||
|
||||
def test_combofield_64(self):
|
||||
def test_combofield_2(self):
|
||||
f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False)
|
||||
self.assertEqual(u'test@example.com', f.clean('test@example.com'))
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com')
|
||||
|
@ -769,12 +769,12 @@ class FieldsTests(TestCase):
|
|||
|
||||
# FilePathField ###############################################################
|
||||
|
||||
def test_filepathfield_65(self):
|
||||
def test_filepathfield_1(self):
|
||||
path = os.path.abspath(forms.__file__)
|
||||
path = os.path.dirname(path) + '/'
|
||||
self.assertTrue(fix_os_paths(path).endswith('/django/forms/'))
|
||||
|
||||
def test_filepathfield_66(self):
|
||||
def test_filepathfield_2(self):
|
||||
path = forms.__file__
|
||||
path = os.path.dirname(os.path.abspath(path)) + '/'
|
||||
f = FilePathField(path=path)
|
||||
|
@ -795,7 +795,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. fields.py is not one of the available choices.']", f.clean, 'fields.py')
|
||||
assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py')
|
||||
|
||||
def test_filepathfield_67(self):
|
||||
def test_filepathfield_3(self):
|
||||
path = forms.__file__
|
||||
path = os.path.dirname(os.path.abspath(path)) + '/'
|
||||
f = FilePathField(path=path, match='^.*?\.py$')
|
||||
|
@ -813,7 +813,7 @@ class FieldsTests(TestCase):
|
|||
self.assertEqual(exp[1], got[1])
|
||||
self.assertTrue(got[0].endswith(exp[0]))
|
||||
|
||||
def test_filepathfield_68(self):
|
||||
def test_filepathfield_4(self):
|
||||
path = os.path.abspath(forms.__file__)
|
||||
path = os.path.dirname(path) + '/'
|
||||
f = FilePathField(path=path, recursive=True, match='^.*?\.py$')
|
||||
|
@ -835,7 +835,7 @@ class FieldsTests(TestCase):
|
|||
|
||||
# SplitDateTimeField ##########################################################
|
||||
|
||||
def test_splitdatetimefield_69(self):
|
||||
def test_splitdatetimefield_1(self):
|
||||
from django.forms.widgets import SplitDateTimeWidget
|
||||
f = SplitDateTimeField()
|
||||
assert isinstance(f.widget, SplitDateTimeWidget)
|
||||
|
@ -847,7 +847,7 @@ class FieldsTests(TestCase):
|
|||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there'])
|
||||
self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30'])
|
||||
|
||||
def test_splitdatetimefield_70(self):
|
||||
def test_splitdatetimefield_2(self):
|
||||
f = SplitDateTimeField(required=False)
|
||||
self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]))
|
||||
self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean(['2006-01-10', '07:30']))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,797 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.forms import Form, CharField, IntegerField, ValidationError
|
||||
from django.forms.formsets import formset_factory, BaseFormSet
|
||||
from django.utils.unittest import TestCase
|
||||
|
||||
|
||||
class Choice(Form):
|
||||
choice = CharField()
|
||||
votes = IntegerField()
|
||||
|
||||
|
||||
# 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.
|
||||
ChoiceFormSet = formset_factory(Choice)
|
||||
|
||||
|
||||
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'])
|
||||
|
||||
|
||||
# Let's define a FormSet that takes a list of favorite drinks, but raises an
|
||||
# error if there are any duplicates. Used in ``test_clean_hook``,
|
||||
# ``test_regression_6926`` & ``test_regression_12878``.
|
||||
FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
|
||||
formset=BaseFavoriteDrinksFormSet, extra=3)
|
||||
|
||||
|
||||
class FormsFormsetTestCase(TestCase):
|
||||
def test_basic_formset(self):
|
||||
# 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')
|
||||
self.assertEqual(str(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_NUM_FORMS" />
|
||||
<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_NUM_FORMS': '0', # 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')
|
||||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual([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()
|
||||
self.assertFalse(formset.is_valid())
|
||||
|
||||
def test_formset_validation(self):
|
||||
# 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_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '',
|
||||
}
|
||||
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.errors, [{'votes': [u'This field is required.']}])
|
||||
|
||||
def test_formset_initial_data(self):
|
||||
# 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')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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_NUM_FORMS': '0', # 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')
|
||||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}])
|
||||
|
||||
def test_second_form_partially_filled(self):
|
||||
# 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_NUM_FORMS': '0', # 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')
|
||||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}])
|
||||
|
||||
def test_delete_prefilled_data(self):
|
||||
# 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_NUM_FORMS': '0', # 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')
|
||||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.errors, [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}])
|
||||
|
||||
def test_displaying_more_than_one_blank_form(self):
|
||||
# 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')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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_NUM_FORMS': '0', # 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')
|
||||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual([form.cleaned_data for form in formset.forms], [{}, {}, {}])
|
||||
|
||||
def test_single_form_completed(self):
|
||||
# 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_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
'choices-1-choice': '',
|
||||
'choices-1-votes': '',
|
||||
'choices-2-choice': '',
|
||||
'choices-2-votes': '',
|
||||
}
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice, extra=3)
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}, {}])
|
||||
|
||||
def test_second_form_partially_filled_2(self):
|
||||
# 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_NUM_FORMS': '0', # 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': '',
|
||||
}
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice, extra=3)
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}, {}])
|
||||
|
||||
def test_more_initial_data(self):
|
||||
# The extra argument also works when the formset is pre-filled with initial
|
||||
# data.
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
'choices-1-choice': '',
|
||||
'choices-1-votes': '', # missing value
|
||||
'choices-2-choice': '',
|
||||
'choices-2-votes': '',
|
||||
}
|
||||
|
||||
initial = [{'choice': u'Calexico', 'votes': 100}]
|
||||
ChoiceFormSet = formset_factory(Choice, extra=3)
|
||||
formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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>""")
|
||||
|
||||
# Make sure retrieving an empty form works, and it shows up in the form list
|
||||
|
||||
self.assertTrue(formset.empty_form.empty_permitted)
|
||||
self.assertEqual(formset.empty_form.as_ul(), """<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>""")
|
||||
|
||||
def test_formset_with_deletion(self):
|
||||
# FormSets with deletion ######################################################
|
||||
# We can easily add deletion ability to a FormSet with an argument 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')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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_NUM_FORMS': '0', # 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')
|
||||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}])
|
||||
self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}])
|
||||
|
||||
# If we fill a form with something and then we check the can_delete checkbox for
|
||||
# that form, that form's errors should not make the entire formset invalid since
|
||||
# it's going to be deleted.
|
||||
|
||||
class CheckForm(Form):
|
||||
field = IntegerField(min_value=100)
|
||||
|
||||
data = {
|
||||
'check-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'check-INITIAL_FORMS': '2', # the number of forms with initial data
|
||||
'check-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'check-0-field': '200',
|
||||
'check-0-DELETE': '',
|
||||
'check-1-field': '50',
|
||||
'check-1-DELETE': 'on',
|
||||
'check-2-field': '',
|
||||
'check-2-DELETE': '',
|
||||
}
|
||||
CheckFormSet = formset_factory(CheckForm, can_delete=True)
|
||||
formset = CheckFormSet(data, prefix='check')
|
||||
self.assertTrue(formset.is_valid())
|
||||
|
||||
# If we remove the deletion flag now we will have our validation back.
|
||||
data['check-1-DELETE'] = ''
|
||||
formset = CheckFormSet(data, prefix='check')
|
||||
self.assertFalse(formset.is_valid())
|
||||
|
||||
# Should be able to get deleted_forms from a valid formset even if a
|
||||
# deleted form would have been invalid.
|
||||
|
||||
class Person(Form):
|
||||
name = CharField()
|
||||
|
||||
PeopleForm = formset_factory(
|
||||
form=Person,
|
||||
can_delete=True)
|
||||
|
||||
p = PeopleForm(
|
||||
{'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
|
||||
'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
|
||||
'form-MAX_NUM_FORMS': 1})
|
||||
|
||||
self.assertTrue(p.is_valid())
|
||||
self.assertEqual(len(p.deleted_forms), 1)
|
||||
|
||||
def test_formsets_with_ordering(self):
|
||||
# FormSets with ordering ######################################################
|
||||
# We can also add ordering ability to a FormSet with an argument 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')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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_NUM_FORMS': '0', # 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')
|
||||
self.assertTrue(formset.is_valid())
|
||||
form_output = []
|
||||
|
||||
for form in formset.ordered_forms:
|
||||
form_output.append(form.cleaned_data)
|
||||
|
||||
self.assertEqual(form_output, [
|
||||
{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'},
|
||||
{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
|
||||
{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
|
||||
])
|
||||
|
||||
def test_empty_ordered_fields(self):
|
||||
# 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_NUM_FORMS': '0', # 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': '',
|
||||
}
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice, can_order=True)
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertTrue(formset.is_valid())
|
||||
form_output = []
|
||||
|
||||
for form in formset.ordered_forms:
|
||||
form_output.append(form.cleaned_data)
|
||||
|
||||
self.assertEqual(form_output, [
|
||||
{'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'},
|
||||
])
|
||||
|
||||
def test_ordering_blank_fieldsets(self):
|
||||
# Ordering should work with blank fieldsets.
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
}
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice, can_order=True)
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertTrue(formset.is_valid())
|
||||
form_output = []
|
||||
|
||||
for form in formset.ordered_forms:
|
||||
form_output.append(form.cleaned_data)
|
||||
|
||||
self.assertEqual(form_output, [])
|
||||
|
||||
def test_formset_with_ordering_and_deletion(self):
|
||||
# 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')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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_NUM_FORMS': '0', # 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')
|
||||
self.assertTrue(formset.is_valid())
|
||||
form_output = []
|
||||
|
||||
for form in formset.ordered_forms:
|
||||
form_output.append(form.cleaned_data)
|
||||
|
||||
self.assertEqual(form_output, [
|
||||
{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'},
|
||||
{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'},
|
||||
])
|
||||
self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}])
|
||||
|
||||
def test_invalid_deleted_form_with_ordering(self):
|
||||
# Should be able to get ordered forms from a valid formset even if a
|
||||
# deleted form would have been invalid.
|
||||
|
||||
class Person(Form):
|
||||
name = CharField()
|
||||
|
||||
PeopleForm = formset_factory(form=Person, can_delete=True, can_order=True)
|
||||
|
||||
p = PeopleForm({
|
||||
'form-0-name': u'',
|
||||
'form-0-DELETE': u'on', # no name!
|
||||
'form-TOTAL_FORMS': 1,
|
||||
'form-INITIAL_FORMS': 1,
|
||||
'form-MAX_NUM_FORMS': 1
|
||||
})
|
||||
|
||||
self.assertTrue(p.is_valid())
|
||||
self.assertEqual(p.ordered_forms, [])
|
||||
|
||||
def test_clean_hook(self):
|
||||
# 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.
|
||||
|
||||
# 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_NUM_FORMS': '0', # max number of forms
|
||||
'drinks-0-name': 'Gin and Tonic',
|
||||
'drinks-1-name': 'Gin and Tonic',
|
||||
}
|
||||
|
||||
formset = FavoriteDrinksFormSet(data, prefix='drinks')
|
||||
self.assertFalse(formset.is_valid())
|
||||
|
||||
# Any errors raised by formset.clean() are available via the
|
||||
# formset.non_form_errors() method.
|
||||
|
||||
for error in formset.non_form_errors():
|
||||
self.assertEqual(str(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_NUM_FORMS': '0', # max number of forms
|
||||
'drinks-0-name': 'Gin and Tonic',
|
||||
'drinks-1-name': 'Bloody Mary',
|
||||
}
|
||||
|
||||
formset = FavoriteDrinksFormSet(data, prefix='drinks')
|
||||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual(formset.non_form_errors(), [])
|
||||
|
||||
def test_limiting_max_forms(self):
|
||||
# Limiting the maximum number of forms ########################################
|
||||
# Base case for max_num.
|
||||
|
||||
# When not passed, max_num will take its default value of None, i.e. unlimited
|
||||
# number of forms, only controlled by the value of the extra parameter.
|
||||
|
||||
LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3)
|
||||
formset = LimitedFavoriteDrinkFormSet()
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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>
|
||||
<tr><th><label for="id_form-2-name">Name:</label></th><td><input type="text" name="form-2-name" id="id_form-2-name" /></td></tr>""")
|
||||
|
||||
# If max_num is 0 then no form is rendered at all.
|
||||
LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=0)
|
||||
formset = LimitedFavoriteDrinkFormSet()
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), "")
|
||||
|
||||
LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
|
||||
formset = LimitedFavoriteDrinkFormSet()
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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 that max_num has no effect when extra is less than max_num.
|
||||
|
||||
LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
|
||||
formset = LimitedFavoriteDrinkFormSet()
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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>""")
|
||||
|
||||
def test_max_num_with_initial_data(self):
|
||||
# max_num with initial data
|
||||
|
||||
# When not passed, max_num will take its default value of None, i.e. unlimited
|
||||
# number of forms, only controlled by the values of the initial and extra
|
||||
# parameters.
|
||||
|
||||
initial = [
|
||||
{'name': 'Fernet and Coke'},
|
||||
]
|
||||
LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1)
|
||||
formset = LimitedFavoriteDrinkFormSet(initial=initial)
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Fernet and Coke" 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>""")
|
||||
|
||||
def test_max_num_zero(self):
|
||||
# If max_num is 0 then no form is rendered at all, even if extra and initial
|
||||
# are specified.
|
||||
|
||||
initial = [
|
||||
{'name': 'Fernet and Coke'},
|
||||
{'name': 'Bloody Mary'},
|
||||
]
|
||||
LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
|
||||
formset = LimitedFavoriteDrinkFormSet(initial=initial)
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), "")
|
||||
|
||||
def test_more_initial_than_max_num(self):
|
||||
# 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)
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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)
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(str(form))
|
||||
|
||||
self.assertEqual('\n'.join(form_output), """<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>""")
|
||||
|
||||
def test_regression_6926(self):
|
||||
# Regression test for #6926 ##################################################
|
||||
# Make sure the management form has the correct prefix.
|
||||
|
||||
formset = FavoriteDrinksFormSet()
|
||||
self.assertEqual(formset.management_form.prefix, 'form')
|
||||
|
||||
formset = FavoriteDrinksFormSet(data={})
|
||||
self.assertEqual(formset.management_form.prefix, 'form')
|
||||
|
||||
formset = FavoriteDrinksFormSet(initial={})
|
||||
self.assertEqual(formset.management_form.prefix, 'form')
|
||||
|
||||
def test_regression_12878(self):
|
||||
# Regression test for #12878 #################################################
|
||||
|
||||
data = {
|
||||
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'drinks-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'drinks-0-name': 'Gin and Tonic',
|
||||
'drinks-1-name': 'Gin and Tonic',
|
||||
}
|
||||
|
||||
formset = FavoriteDrinksFormSet(data, prefix='drinks')
|
||||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.non_form_errors(), [u'You may only specify a drink once.'])
|
||||
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '1', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
}
|
||||
|
||||
class Choice(Form):
|
||||
choice = CharField()
|
||||
votes = IntegerField()
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice)
|
||||
|
||||
class FormsetAsFooTests(TestCase):
|
||||
def test_as_table(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
|
||||
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
|
||||
|
||||
def test_as_p(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
|
||||
<p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
|
||||
|
||||
def test_as_ul(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
|
|
@ -0,0 +1,460 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings
|
||||
from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget
|
||||
from django.utils.unittest import TestCase
|
||||
|
||||
|
||||
class FormsMediaTestCase(TestCase):
|
||||
# Tests for the media handling on widgets and forms
|
||||
def setUp(self):
|
||||
super(FormsMediaTestCase, self).setUp()
|
||||
self.original_media_url = settings.MEDIA_URL
|
||||
settings.MEDIA_URL = 'http://media.example.com/media/'
|
||||
|
||||
def tearDown(self):
|
||||
settings.MEDIA_URL = self.original_media_url
|
||||
super(FormsMediaTestCase, self).tearDown()
|
||||
|
||||
def test_construction(self):
|
||||
# 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'))
|
||||
self.assertEqual(str(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)
|
||||
self.assertEqual(str(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()
|
||||
self.assertEqual(str(w.media), '')
|
||||
|
||||
def test_media_dsl(self):
|
||||
###############################################################
|
||||
# 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()
|
||||
self.assertEqual(str(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
|
||||
self.assertEqual(str(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" />""")
|
||||
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_combine_media(self):
|
||||
# Media objects can be combined. Any given media resource will appear only
|
||||
# once. Duplicated media definitions are ignored.
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
w1 = MyWidget1()
|
||||
w2 = MyWidget2()
|
||||
w3 = MyWidget3()
|
||||
self.assertEqual(str(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
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
# Regression check for #12879: specifying the same CSS or JS file
|
||||
# multiple times in a single Media instance should result in that file
|
||||
# only being included once.
|
||||
class MyWidget4(TextInput):
|
||||
class Media:
|
||||
css = {'all': ('/path/to/css1', '/path/to/css1')}
|
||||
js = ('/path/to/js1', '/path/to/js1')
|
||||
|
||||
w4 = MyWidget4()
|
||||
self.assertEqual(str(w4.media), """<link href="/path/to/css1" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="/path/to/js1"></script>""")
|
||||
|
||||
def test_media_property(self):
|
||||
###############################################################
|
||||
# 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()
|
||||
self.assertEqual(str(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()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_media_property_parent_references(self):
|
||||
# Media properties can reference the media of their parents,
|
||||
# even if the parent media was defined using a class
|
||||
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')
|
||||
|
||||
class MyWidget6(MyWidget1):
|
||||
def _media(self):
|
||||
return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
|
||||
media = property(_media)
|
||||
|
||||
w6 = MyWidget6()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_media_inheritance(self):
|
||||
###############################################################
|
||||
# Inheritance of media
|
||||
###############################################################
|
||||
|
||||
# If a widget extends another but provides no media definition, it inherits the parent widget's media
|
||||
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')
|
||||
|
||||
class MyWidget7(MyWidget1):
|
||||
pass
|
||||
|
||||
w7 = MyWidget7()
|
||||
self.assertEqual(str(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()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_media_inheritance_from_property(self):
|
||||
# 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 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')
|
||||
|
||||
class MyWidget4(TextInput):
|
||||
def _media(self):
|
||||
return Media(css={'all': ('/some/path',)}, js = ('/some/js',))
|
||||
media = property(_media)
|
||||
|
||||
class MyWidget9(MyWidget4):
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('/other/path',)
|
||||
}
|
||||
js = ('/other/js',)
|
||||
|
||||
w9 = MyWidget9()
|
||||
self.assertEqual(str(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()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_media_inheritance_extends(self):
|
||||
# A widget can explicitly enable full media inheritance by specifying 'extend=True'
|
||||
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')
|
||||
|
||||
class MyWidget11(MyWidget1):
|
||||
class Media:
|
||||
extend = True
|
||||
css = {
|
||||
'all': ('/path/to/css3','path/to/css1')
|
||||
}
|
||||
js = ('/path/to/js1','/path/to/js4')
|
||||
|
||||
w11 = MyWidget11()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_media_inheritance_single_type(self):
|
||||
# A widget can enable inheritance of one media type by specifying extend as a tuple
|
||||
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')
|
||||
|
||||
class MyWidget12(MyWidget1):
|
||||
class Media:
|
||||
extend = ('css',)
|
||||
css = {
|
||||
'all': ('/path/to/css3','path/to/css1')
|
||||
}
|
||||
js = ('/path/to/js1','/path/to/js4')
|
||||
|
||||
w12 = MyWidget12()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_multi_media(self):
|
||||
###############################################################
|
||||
# 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()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_multi_widget(self):
|
||||
###############################################################
|
||||
# Multiwidget media handling
|
||||
###############################################################
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
# 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()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
def test_form_media(self):
|
||||
###############################################################
|
||||
# Media processing for forms
|
||||
###############################################################
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
# 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()
|
||||
self.assertEqual(str(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()
|
||||
self.assertEqual(str(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()
|
||||
self.assertEqual(str(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>""")
|
||||
|
||||
# Media works in templates
|
||||
from django.template import Template, Context
|
||||
self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """<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><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" />""")
|
|
@ -0,0 +1,161 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.forms import Form, ModelForm, FileField, ModelChoiceField
|
||||
from django.test import TestCase
|
||||
from regressiontests.forms.models import ChoiceModel, ChoiceOptionModel, ChoiceFieldModel, FileModel, Group, BoundaryModel, Defaults
|
||||
|
||||
|
||||
class ChoiceFieldForm(ModelForm):
|
||||
class Meta:
|
||||
model = ChoiceFieldModel
|
||||
|
||||
|
||||
class FileForm(Form):
|
||||
file1 = FileField()
|
||||
|
||||
|
||||
class TestTicket12510(TestCase):
|
||||
''' It is not necessary to generate choices for ModelChoiceField (regression test for #12510). '''
|
||||
def setUp(self):
|
||||
self.groups = [Group.objects.create(name=name) for name in 'abc']
|
||||
|
||||
def test_choices_not_fetched_when_not_rendering(self):
|
||||
def test():
|
||||
field = ModelChoiceField(Group.objects.order_by('-name'))
|
||||
self.assertEqual('a', field.clean(self.groups[0].pk).name)
|
||||
# only one query is required to pull the model from DB
|
||||
self.assertNumQueries(1, test)
|
||||
|
||||
class ModelFormCallableModelDefault(TestCase):
|
||||
def test_no_empty_option(self):
|
||||
"If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)."
|
||||
option = ChoiceOptionModel.objects.create(name='default')
|
||||
|
||||
choices = list(ChoiceFieldForm().fields['choice'].choices)
|
||||
self.assertEquals(len(choices), 1)
|
||||
self.assertEquals(choices[0], (option.pk, unicode(option)))
|
||||
|
||||
def test_callable_initial_value(self):
|
||||
"The initial value for a callable default returning a queryset is the pk (refs #13769)"
|
||||
obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
|
||||
obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
|
||||
obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
|
||||
self.assertEquals(ChoiceFieldForm().as_p(), """<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice" value="1" id="initial-id_choice" /></p>
|
||||
<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" id="id_choice_int">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice_int" value="1" id="initial-id_choice_int" /></p>
|
||||
<p><label for="id_multi_choice">Multi choice:</label> <select multiple="multiple" name="multi_choice" id="id_multi_choice">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice" value="1" id="initial-id_multi_choice_0" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>
|
||||
<p><label for="id_multi_choice_int">Multi choice int:</label> <select multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
|
||||
<option value="1" selected="selected">ChoiceOption 1</option>
|
||||
<option value="2">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice_int" value="1" id="initial-id_multi_choice_int_0" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>""")
|
||||
|
||||
def test_initial_instance_value(self):
|
||||
"Initial instances for model fields may also be instances (refs #7287)"
|
||||
obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
|
||||
obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
|
||||
obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
|
||||
self.assertEquals(ChoiceFieldForm(initial={
|
||||
'choice': obj2,
|
||||
'choice_int': obj2,
|
||||
'multi_choice': [obj2,obj3],
|
||||
'multi_choice_int': ChoiceOptionModel.objects.exclude(name="default"),
|
||||
}).as_p(), """<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice" value="2" id="initial-id_choice" /></p>
|
||||
<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" id="id_choice_int">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-choice_int" value="2" id="initial-id_choice_int" /></p>
|
||||
<p><label for="id_multi_choice">Multi choice:</label> <select multiple="multiple" name="multi_choice" id="id_multi_choice">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3" selected="selected">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice" value="2" id="initial-id_multi_choice_0" />
|
||||
<input type="hidden" name="initial-multi_choice" value="3" id="initial-id_multi_choice_1" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>
|
||||
<p><label for="id_multi_choice_int">Multi choice int:</label> <select multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
|
||||
<option value="1">ChoiceOption 1</option>
|
||||
<option value="2" selected="selected">ChoiceOption 2</option>
|
||||
<option value="3" selected="selected">ChoiceOption 3</option>
|
||||
</select><input type="hidden" name="initial-multi_choice_int" value="2" id="initial-id_multi_choice_int_0" />
|
||||
<input type="hidden" name="initial-multi_choice_int" value="3" id="initial-id_multi_choice_int_1" /> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>""")
|
||||
|
||||
|
||||
|
||||
class FormsModelTestCase(TestCase):
|
||||
def test_unicode_filename(self):
|
||||
# FileModel with unicode filename and data #########################
|
||||
f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False)
|
||||
self.assertTrue(f.is_valid())
|
||||
self.assertTrue('file1' in f.cleaned_data)
|
||||
m = FileModel.objects.create(file=f.cleaned_data['file1'])
|
||||
self.assertEqual(m.file.name, u'tests/\u6211\u96bb\u6c23\u588a\u8239\u88dd\u6eff\u6652\u9c54.txt')
|
||||
m.delete()
|
||||
|
||||
def test_boundary_conditions(self):
|
||||
# Boundary conditions on a PostitiveIntegerField #########################
|
||||
class BoundaryForm(ModelForm):
|
||||
class Meta:
|
||||
model = BoundaryModel
|
||||
|
||||
f = BoundaryForm({'positive_integer': 100})
|
||||
self.assertTrue(f.is_valid())
|
||||
f = BoundaryForm({'positive_integer': 0})
|
||||
self.assertTrue(f.is_valid())
|
||||
f = BoundaryForm({'positive_integer': -100})
|
||||
self.assertFalse(f.is_valid())
|
||||
|
||||
def test_formfield_initial(self):
|
||||
# Formfield initial values ########
|
||||
# If the model has default values for some fields, they are used as the formfield
|
||||
# initial values.
|
||||
class DefaultsForm(ModelForm):
|
||||
class Meta:
|
||||
model = Defaults
|
||||
|
||||
self.assertEqual(DefaultsForm().fields['name'].initial, u'class default value')
|
||||
self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))
|
||||
self.assertEqual(DefaultsForm().fields['value'].initial, 42)
|
||||
r1 = DefaultsForm()['callable_default'].as_widget()
|
||||
r2 = DefaultsForm()['callable_default'].as_widget()
|
||||
self.assertNotEqual(r1, r2)
|
||||
|
||||
# In a ModelForm that is passed an instance, the initial values come from the
|
||||
# instance's values, not the model's defaults.
|
||||
foo_instance = Defaults(name=u'instance value', def_date=datetime.date(1969, 4, 4), value=12)
|
||||
instance_form = DefaultsForm(instance=foo_instance)
|
||||
self.assertEqual(instance_form.initial['name'], u'instance value')
|
||||
self.assertEqual(instance_form.initial['def_date'], datetime.date(1969, 4, 4))
|
||||
self.assertEqual(instance_form.initial['value'], 12)
|
||||
|
||||
from django.forms import CharField
|
||||
|
||||
class ExcludingForm(ModelForm):
|
||||
name = CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
model = Defaults
|
||||
exclude = ['name', 'callable_default']
|
||||
|
||||
f = ExcludingForm({'name': u'Hello', 'value': 99, 'def_date': datetime.date(1999, 3, 2)})
|
||||
self.assertTrue(f.is_valid())
|
||||
self.assertEqual(f.cleaned_data['name'], u'Hello')
|
||||
obj = f.save()
|
||||
self.assertEqual(obj.name, u'class default value')
|
||||
self.assertEqual(obj.value, 99)
|
||||
self.assertEqual(obj.def_date, datetime.date(1999, 3, 2))
|
|
@ -0,0 +1,122 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.forms import *
|
||||
from django.utils.unittest import TestCase
|
||||
from django.utils.translation import ugettext_lazy, activate, deactivate
|
||||
|
||||
class FormsRegressionsTestCase(TestCase):
|
||||
def test_class(self):
|
||||
# Tests to prevent against recurrences of earlier bugs.
|
||||
extra_attrs = {'class': 'special'}
|
||||
|
||||
class TestForm(Form):
|
||||
f1 = CharField(max_length=10, widget=TextInput(attrs=extra_attrs))
|
||||
f2 = CharField(widget=TextInput(attrs=extra_attrs))
|
||||
|
||||
self.assertEqual(TestForm(auto_id=False).as_p(), u'<p>F1: <input type="text" class="special" name="f1" maxlength="10" /></p>\n<p>F2: <input type="text" class="special" name="f2" /></p>')
|
||||
|
||||
def test_regression_3600(self):
|
||||
# Tests for form i18n #
|
||||
# There were some problems with form translations in #3600
|
||||
|
||||
class SomeForm(Form):
|
||||
username = CharField(max_length=10, label=ugettext_lazy('Username'))
|
||||
|
||||
f = SomeForm()
|
||||
self.assertEqual(f.as_p(), '<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>')
|
||||
|
||||
# Translations are done at rendering time, so multi-lingual apps can define forms)
|
||||
activate('de')
|
||||
self.assertEqual(f.as_p(), '<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>')
|
||||
activate('pl')
|
||||
self.assertEqual(f.as_p(), u'<p><label for="id_username">Nazwa u\u017cytkownika:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>')
|
||||
deactivate()
|
||||
|
||||
def test_regression_5216(self):
|
||||
# There was some problems with form translations in #5216
|
||||
class SomeForm(Form):
|
||||
field_1 = CharField(max_length=10, label=ugettext_lazy('field_1'))
|
||||
field_2 = CharField(max_length=10, label=ugettext_lazy('field_2'), widget=TextInput(attrs={'id': 'field_2_id'}))
|
||||
|
||||
f = SomeForm()
|
||||
self.assertEqual(f['field_1'].label_tag(), '<label for="id_field_1">field_1</label>')
|
||||
self.assertEqual(f['field_2'].label_tag(), '<label for="field_2_id">field_2</label>')
|
||||
|
||||
# Unicode decoding problems...
|
||||
GENDERS = ((u'\xc5', u'En tied\xe4'), (u'\xf8', u'Mies'), (u'\xdf', u'Nainen'))
|
||||
|
||||
class SomeForm(Form):
|
||||
somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf')
|
||||
|
||||
f = SomeForm()
|
||||
self.assertEqual(f.as_p(), u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>')
|
||||
|
||||
# Testing choice validation with UTF-8 bytestrings as input (these are the
|
||||
# Russian abbreviations "мес." and "шт.".
|
||||
UNITS = (('\xd0\xbc\xd0\xb5\xd1\x81.', '\xd0\xbc\xd0\xb5\xd1\x81.'), ('\xd1\x88\xd1\x82.', '\xd1\x88\xd1\x82.'))
|
||||
f = ChoiceField(choices=UNITS)
|
||||
self.assertEqual(f.clean(u'\u0448\u0442.'), u'\u0448\u0442.')
|
||||
self.assertEqual(f.clean('\xd1\x88\xd1\x82.'), u'\u0448\u0442.')
|
||||
|
||||
# Translated error messages used to be buggy.
|
||||
activate('ru')
|
||||
f = SomeForm({})
|
||||
self.assertEqual(f.as_p(), u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>')
|
||||
deactivate()
|
||||
|
||||
# Deep copying translated text shouldn't raise an error)
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
class CopyForm(Form):
|
||||
degree = IntegerField(widget=Select(choices=((1, gettext_lazy('test')),)))
|
||||
|
||||
f = CopyForm()
|
||||
|
||||
def test_misc(self):
|
||||
# There once was a problem with Form fields called "data". Let's make sure that
|
||||
# doesn't come back.
|
||||
class DataForm(Form):
|
||||
data = CharField(max_length=10)
|
||||
|
||||
f = DataForm({'data': 'xyzzy'})
|
||||
self.assertTrue(f.is_valid())
|
||||
self.assertEqual(f.cleaned_data, {'data': u'xyzzy'})
|
||||
|
||||
# A form with *only* hidden fields that has errors is going to be very unusual.
|
||||
class HiddenForm(Form):
|
||||
data = IntegerField(widget=HiddenInput)
|
||||
|
||||
f = HiddenForm({})
|
||||
self.assertEqual(f.as_p(), u'<ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul>\n<p> <input type="hidden" name="data" id="id_data" /></p>')
|
||||
self.assertEqual(f.as_table(), u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>')
|
||||
|
||||
def test_xss_error_messages(self):
|
||||
###################################################
|
||||
# Tests for XSS vulnerabilities in error messages #
|
||||
###################################################
|
||||
|
||||
# The forms layer doesn't escape input values directly because error messages
|
||||
# might be presented in non-HTML contexts. Instead, the message is just marked
|
||||
# for escaping by the template engine. So we'll need to construct a little
|
||||
# silly template to trigger the escaping.
|
||||
from django.template import Template, Context
|
||||
t = Template('{{ form.errors }}')
|
||||
|
||||
class SomeForm(Form):
|
||||
field = ChoiceField(choices=[('one', 'One')])
|
||||
|
||||
f = SomeForm({'field': '<script>'})
|
||||
self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>')
|
||||
|
||||
class SomeForm(Form):
|
||||
field = MultipleChoiceField(choices=[('one', 'One')])
|
||||
|
||||
f = SomeForm({'field': ['<script>']})
|
||||
self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>')
|
||||
|
||||
from regressiontests.forms.models import ChoiceModel
|
||||
|
||||
class SomeForm(Form):
|
||||
field = ModelMultipleChoiceField(ChoiceModel.objects.all())
|
||||
|
||||
f = SomeForm({'field': ['<script>']})
|
||||
self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>"<script>" is not a valid value for a primary key.</li></ul></li></ul>')
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms.util import *
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.utils.unittest import TestCase
|
||||
|
||||
|
||||
class FormsUtilTestCase(TestCase):
|
||||
# Tests for forms/util.py module.
|
||||
|
||||
def test_flatatt(self):
|
||||
###########
|
||||
# flatatt #
|
||||
###########
|
||||
|
||||
self.assertEqual(flatatt({'id': "header"}), u' id="header"')
|
||||
self.assertEqual(flatatt({'class': "news", 'title': "Read this"}), u' class="news" title="Read this"')
|
||||
self.assertEqual(flatatt({}), u'')
|
||||
|
||||
def test_validation_error(self):
|
||||
###################
|
||||
# ValidationError #
|
||||
###################
|
||||
|
||||
# Can take a string.
|
||||
self.assertEqual(str(ErrorList(ValidationError("There was an error.").messages)),
|
||||
'<ul class="errorlist"><li>There was an error.</li></ul>')
|
||||
|
||||
# Can take a unicode string.
|
||||
self.assertEqual(str(ErrorList(ValidationError(u"Not \u03C0.").messages)),
|
||||
'<ul class="errorlist"><li>Not π.</li></ul>')
|
||||
|
||||
# Can take a lazy string.
|
||||
self.assertEqual(str(ErrorList(ValidationError(ugettext_lazy("Error.")).messages)),
|
||||
'<ul class="errorlist"><li>Error.</li></ul>')
|
||||
|
||||
# Can take a list.
|
||||
self.assertEqual(str(ErrorList(ValidationError(["Error one.", "Error two."]).messages)),
|
||||
'<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>')
|
||||
|
||||
# Can take a mixture in a list.
|
||||
self.assertEqual(str(ErrorList(ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages)),
|
||||
'<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>')
|
||||
|
||||
class VeryBadError:
|
||||
def __unicode__(self): return u"A very bad error."
|
||||
|
||||
# Can take a non-string.
|
||||
self.assertEqual(str(ErrorList(ValidationError(VeryBadError()).messages)),
|
||||
'<ul class="errorlist"><li>A very bad error.</li></ul>')
|
||||
|
||||
# Escapes non-safe input but not input marked safe.
|
||||
example = 'Example of link: <a href="http://www.example.com/">example</a>'
|
||||
self.assertEqual(str(ErrorList([example])),
|
||||
'<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>')
|
||||
self.assertEqual(str(ErrorList([mark_safe(example)])),
|
||||
'<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>')
|
File diff suppressed because it is too large
Load Diff
|
@ -1,60 +0,0 @@
|
|||
# coding: utf-8
|
||||
"""
|
||||
Tests for forms/util.py module.
|
||||
"""
|
||||
|
||||
tests = r"""
|
||||
>>> from django.forms.util import *
|
||||
>>> from django.core.exceptions import ValidationError
|
||||
>>> from django.utils.translation import ugettext_lazy
|
||||
|
||||
###########
|
||||
# flatatt #
|
||||
###########
|
||||
|
||||
>>> from django.forms.util import flatatt
|
||||
>>> flatatt({'id': "header"})
|
||||
u' id="header"'
|
||||
>>> flatatt({'class': "news", 'title': "Read this"})
|
||||
u' class="news" title="Read this"'
|
||||
>>> flatatt({})
|
||||
u''
|
||||
|
||||
###################
|
||||
# ValidationError #
|
||||
###################
|
||||
|
||||
# Can take a string.
|
||||
>>> print ErrorList(ValidationError("There was an error.").messages)
|
||||
<ul class="errorlist"><li>There was an error.</li></ul>
|
||||
|
||||
# Can take a unicode string.
|
||||
>>> print ErrorList(ValidationError(u"Not \u03C0.").messages)
|
||||
<ul class="errorlist"><li>Not π.</li></ul>
|
||||
|
||||
# Can take a lazy string.
|
||||
>>> print ErrorList(ValidationError(ugettext_lazy("Error.")).messages)
|
||||
<ul class="errorlist"><li>Error.</li></ul>
|
||||
|
||||
# Can take a list.
|
||||
>>> print ErrorList(ValidationError(["Error one.", "Error two."]).messages)
|
||||
<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
|
||||
|
||||
# Can take a mixture in a list.
|
||||
>>> print ErrorList(ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages)
|
||||
<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
|
||||
|
||||
>>> class VeryBadError:
|
||||
... def __unicode__(self): return u"A very bad error."
|
||||
|
||||
# Can take a non-string.
|
||||
>>> print ErrorList(ValidationError(VeryBadError()).messages)
|
||||
<ul class="errorlist"><li>A very bad error.</li></ul>
|
||||
|
||||
# Escapes non-safe input but not input marked safe.
|
||||
>>> example = 'Example of link: <a href="http://www.example.com/">example</a>'
|
||||
>>> print ErrorList([example])
|
||||
<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>
|
||||
>>> print ErrorList([mark_safe(example)])
|
||||
<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>
|
||||
"""
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue