' % e for e in self]))
-
- # This form should print errors the default way.
- form1 = TestForm({'first_name': 'John'})
- self.assertHTMLEqual(str(form1['last_name'].errors), '
')
-
- # This one should wrap error groups in the customized way.
- form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList)
- self.assertHTMLEqual(str(form2['last_name'].errors), '
')
-
-
-class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin):
- def test_modelchoicefield(self):
- # Create choices for the model choice field tests below.
- from forms_tests.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(['REQUIRED'], f.clean, '')
- self.assertFormErrors(['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(['REQUIRED'], f.clean, '')
- self.assertFormErrors(['NOT A LIST OF VALUES'], f.clean, '3')
- self.assertFormErrors(['4 IS INVALID CHOICE'], f.clean, ['4'])
diff --git a/tests/forms_tests/tests/extra.py b/tests/forms_tests/tests/extra.py
deleted file mode 100644
index 427d099bb2..0000000000
--- a/tests/forms_tests/tests/extra.py
+++ /dev/null
@@ -1,807 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
-import datetime
-
-from django.forms import *
-from django.forms.extras import SelectDateWidget
-from django.forms.util import ErrorList
-from django.test import TestCase
-from django.test.utils import override_settings
-from django.utils import six
-from django.utils import translation
-from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
-
-from .error_messages import AssertFormErrorsMixin
-
-
-class GetDate(Form):
- mydate = DateField(widget=SelectDateWidget)
-
-class GetNotRequiredDate(Form):
- mydate = DateField(widget=SelectDateWidget, required=False)
-
-class GetDateShowHiddenInitial(Form):
- mydate = DateField(widget=SelectDateWidget, show_hidden_initial=True)
-
-class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
- ###############
- # Extra stuff #
- ###############
-
- # 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.assertHTMLEqual(w.render('mydate', ''), """
-
-
-
-""")
- self.assertHTMLEqual(w.render('mydate', None), w.render('mydate', ''))
-
- self.assertHTMLEqual(w.render('mydate', '2010-04-15'), """
-
-""")
-
- # Accepts a datetime or a string:
- self.assertHTMLEqual(w.render('mydate', datetime.date(2010, 4, 15)), w.render('mydate', '2010-04-15'))
-
- # Invalid dates still render the failed date:
- self.assertHTMLEqual(w.render('mydate', '2010-02-31'), """
-
-""")
-
- # Using a SelectDateWidget in a form:
- w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), required=False)
- self.assertHTMLEqual(w.render('mydate', ''), """
-
-""")
- self.assertHTMLEqual(w.render('mydate', '2010-04-15'), """
-
-""")
-
- 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.assertHTMLEqual(a['mydate'].as_hidden(), '')
-
- b = GetDate({'mydate':'2008-4-1'})
- self.assertTrue(b.is_valid())
- self.assertEqual(b.cleaned_data['mydate'], datetime.date(2008, 4, 1))
-
- # Invalid dates shouldn't be allowed
- c = GetDate({'mydate_month':'2', 'mydate_day':'31', 'mydate_year':'2010'})
- self.assertFalse(c.is_valid())
- self.assertEqual(c.errors, {'mydate': ['Enter a valid date.']})
-
- # label tag is correctly associated with month dropdown
- d = GetDate({'mydate_month':'1', 'mydate_day':'1', 'mydate_year':'2010'})
- self.assertTrue('
""")
-
- def test_form_with_iterable_boundfield(self):
- class BeatleForm(Form):
- name = ChoiceField(choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')], widget=RadioSelect)
-
- f = BeatleForm(auto_id=False)
- self.assertHTMLEqual('\n'.join([str(bf) for bf in f['name']]), """ John
- Paul
- George
- Ringo""")
- self.assertHTMLEqual('\n'.join(['
%s
' % bf for bf in f['name']]), """
John
-
Paul
-
George
-
Ringo
""")
-
- def test_form_with_noniterable_boundfield(self):
- # You can iterate over any BoundField, not just those with widget=RadioSelect.
- class BeatleForm(Form):
- name = CharField()
-
- f = BeatleForm(auto_id=False)
- self.assertHTMLEqual('\n'.join([str(bf) for bf in f['name']]), '')
-
- def test_forms_with_multiple_choice(self):
- # MultipleChoiceField is a special case, as its data is required to be a list:
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField()
-
- f = SongForm(auto_id=False)
- self.assertHTMLEqual(str(f['composers']), """""")
-
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')])
-
- f = SongForm(auto_id=False)
- self.assertHTMLEqual(str(f['composers']), """""")
- f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
- self.assertHTMLEqual(str(f['name']), '')
- self.assertHTMLEqual(str(f['composers']), """""")
-
- def test_hidden_data(self):
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')])
-
- # MultipleChoiceField rendered as_hidden() is a special case. Because it can
- # have multiple values, its as_hidden() renders multiple
- # tags.
- f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
- self.assertHTMLEqual(f['composers'].as_hidden(), '')
- f = SongForm({'name': 'From Me To You', 'composers': ['P', 'J']}, auto_id=False)
- self.assertHTMLEqual(f['composers'].as_hidden(), """
-""")
-
- # DateTimeField rendered as_hidden() is special too
- class MessageForm(Form):
- when = SplitDateTimeField()
-
- f = MessageForm({'when_0': '1992-01-01', 'when_1': '01:01'})
- self.assertTrue(f.is_valid())
- self.assertHTMLEqual(str(f['when']), '')
- self.assertHTMLEqual(f['when'].as_hidden(), '')
-
- def test_mulitple_choice_checkbox(self):
- # MultipleChoiceField can also be used with the CheckboxSelectMultiple widget.
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
-
- f = SongForm(auto_id=False)
- self.assertHTMLEqual(str(f['composers']), """
-
John Lennon
-
Paul McCartney
-
""")
- f = SongForm({'composers': ['J']}, auto_id=False)
- self.assertHTMLEqual(str(f['composers']), """
""")
-
- def test_checkbox_auto_id(self):
- # Regarding auto_id, CheckboxSelectMultiple is a special case. Each checkbox
- # gets a distinct ID, formed by appending an underscore plus the checkbox's
- # zero-based index.
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
-
- f = SongForm(auto_id='%s_id')
- self.assertHTMLEqual(str(f['composers']), """
-
John Lennon
-
Paul McCartney
-
""")
-
- def test_multiple_choice_list_data(self):
- # Data for a MultipleChoiceField should be a list. QueryDict, MultiValueDict and
- # MergeDict (when created as a merge of MultiValueDicts) conveniently work with
- # this.
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
-
- data = {'name': 'Yesterday', 'composers': ['J', 'P']}
- f = SongForm(data)
- self.assertEqual(f.errors, {})
-
- data = QueryDict('name=Yesterday&composers=J&composers=P')
- f = SongForm(data)
- self.assertEqual(f.errors, {})
-
- data = MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P']))
- f = SongForm(data)
- self.assertEqual(f.errors, {})
-
- data = MergeDict(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])))
- f = SongForm(data)
- self.assertEqual(f.errors, {})
-
- def test_multiple_hidden(self):
- class SongForm(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
-
- # The MultipleHiddenInput widget renders multiple values as hidden fields.
- class SongFormHidden(Form):
- name = CharField()
- composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
-
- f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
- self.assertHTMLEqual(f.as_ul(), """
Name:
-
""")
-
- # When using CheckboxSelectMultiple, the framework expects a list of input and
- # returns a list of input.
- f = SongForm({'name': 'Yesterday'}, auto_id=False)
- self.assertEqual(f.errors['composers'], ['This field is required.'])
- f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
- self.assertEqual(f.errors, {})
- self.assertEqual(f.cleaned_data['composers'], ['J'])
- self.assertEqual(f.cleaned_data['name'], 'Yesterday')
- f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
- self.assertEqual(f.errors, {})
- self.assertEqual(f.cleaned_data['composers'], ['J', 'P'])
- self.assertEqual(f.cleaned_data['name'], 'Yesterday')
-
- def test_escaping(self):
- # Validation errors are HTML-escaped when output as HTML.
- class EscapingForm(Form):
- special_name = CharField(label="Special Field")
- special_safe_name = CharField(label=mark_safe("Special Field"))
-
- def clean_special_name(self):
- raise ValidationError("Something's wrong with '%s'" % self.cleaned_data['special_name'])
-
- def clean_special_safe_name(self):
- raise ValidationError(mark_safe("'%s' is a safe string" % self.cleaned_data['special_safe_name']))
-
- f = EscapingForm({'special_name': "Nothing to escape", 'special_safe_name': "Nothing to escape"}, auto_id=False)
- self.assertHTMLEqual(f.as_table(), """
<em>Special</em> Field:
Something's wrong with 'Nothing to escape'
-
Special Field:
'Nothing to escape' is a safe string
""")
- f = EscapingForm({
- 'special_name': "Should escape < & > and ",
- 'special_safe_name': "Do not escape"
- }, auto_id=False)
- self.assertHTMLEqual(f.as_table(), """
<em>Special</em> Field:
Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'
-
Special Field:
'Do not escape' is a safe string
""")
-
- def test_validating_multiple_fields(self):
- # There are a couple of ways to do multiple-field validation. If you want the
- # validation message to be associated with a particular field, implement the
- # clean_XXX() method on the Form, where XXX is the field name. As in
- # Field.clean(), the clean_XXX() method should return the cleaned value. In the
- # clean_XXX() method, you have access to self.cleaned_data, which is a dictionary
- # of all the data that has been cleaned *so far*, in order by the fields,
- # including the current field (e.g., the field XXX if you're in clean_XXX()).
- class UserRegistration(Form):
- username = CharField(max_length=10)
- password1 = CharField(widget=PasswordInput)
- password2 = CharField(widget=PasswordInput)
-
- def clean_password2(self):
- if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
- raise ValidationError('Please make sure your passwords match.')
-
- return self.cleaned_data['password2']
-
- f = UserRegistration(auto_id=False)
- self.assertEqual(f.errors, {})
- f = UserRegistration({}, auto_id=False)
- self.assertEqual(f.errors['username'], ['This field is required.'])
- self.assertEqual(f.errors['password1'], ['This field is required.'])
- self.assertEqual(f.errors['password2'], ['This field is required.'])
- f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
- self.assertEqual(f.errors['password2'], ['Please make sure your passwords match.'])
- f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
- self.assertEqual(f.errors, {})
- self.assertEqual(f.cleaned_data['username'], 'adrian')
- self.assertEqual(f.cleaned_data['password1'], 'foo')
- self.assertEqual(f.cleaned_data['password2'], 'foo')
-
- # Another way of doing multiple-field validation is by implementing the
- # Form's clean() method. If you do this, any ValidationError raised by that
- # method will not be associated with a particular field; it will have a
- # special-case association with the field named '__all__'.
- # Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of
- # all the fields/values that have *not* raised a ValidationError. Also note
- # Form.clean() is required to return a dictionary of all clean data.
- class UserRegistration(Form):
- username = CharField(max_length=10)
- password1 = CharField(widget=PasswordInput)
- password2 = CharField(widget=PasswordInput)
-
- def clean(self):
- if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
- raise ValidationError('Please make sure your passwords match.')
-
- return self.cleaned_data
-
- f = UserRegistration(auto_id=False)
- self.assertEqual(f.errors, {})
- f = UserRegistration({}, auto_id=False)
- self.assertHTMLEqual(f.as_table(), """
Username:
This field is required.
-
Password1:
This field is required.
-
Password2:
This field is required.
""")
- self.assertEqual(f.errors['username'], ['This field is required.'])
- self.assertEqual(f.errors['password1'], ['This field is required.'])
- self.assertEqual(f.errors['password2'], ['This field is required.'])
- f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
- self.assertEqual(f.errors['__all__'], ['Please make sure your passwords match.'])
- self.assertHTMLEqual(f.as_table(), """
Please make sure your passwords match.
-
Username:
-
Password1:
-
Password2:
""")
- self.assertHTMLEqual(f.as_ul(), """
Please make sure your passwords match.
-
Username:
-
Password1:
-
Password2:
""")
- f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
- self.assertEqual(f.errors, {})
- self.assertEqual(f.cleaned_data['username'], 'adrian')
- self.assertEqual(f.cleaned_data['password1'], 'foo')
- self.assertEqual(f.cleaned_data['password2'], 'foo')
-
- def test_dynamic_construction(self):
- # It's possible to construct a Form dynamically by adding to the self.fields
- # dictionary in __init__(). Don't forget to call Form.__init__() within the
- # subclass' __init__().
- class Person(Form):
- first_name = CharField()
- last_name = CharField()
-
- def __init__(self, *args, **kwargs):
- super(Person, self).__init__(*args, **kwargs)
- self.fields['birthday'] = DateField()
-
- p = Person(auto_id=False)
- self.assertHTMLEqual(p.as_table(), """
First name:
-
Last name:
-
Birthday:
""")
-
- # Instances of a dynamic Form do not persist fields from one Form instance to
- # the next.
- class MyForm(Form):
- def __init__(self, data=None, auto_id=False, field_list=[]):
- Form.__init__(self, data, auto_id=auto_id)
-
- for field in field_list:
- self.fields[field[0]] = field[1]
-
- field_list = [('field1', CharField()), ('field2', CharField())]
- my_form = MyForm(field_list=field_list)
- self.assertHTMLEqual(my_form.as_table(), """
""")
-
- # Similarly, changes to field attributes do not persist from one Form instance
- # to the next.
- class Person(Form):
- first_name = CharField(required=False)
- last_name = CharField(required=False)
-
- def __init__(self, names_required=False, *args, **kwargs):
- super(Person, self).__init__(*args, **kwargs)
-
- if names_required:
- self.fields['first_name'].required = True
- self.fields['first_name'].widget.attrs['class'] = 'required'
- self.fields['last_name'].required = True
- self.fields['last_name'].widget.attrs['class'] = 'required'
-
- f = Person(names_required=False)
- self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (False, False))
- self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({}, {}))
- f = Person(names_required=True)
- self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (True, True))
- self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({'class': 'required'}, {'class': 'required'}))
- f = Person(names_required=False)
- self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (False, False))
- self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({}, {}))
-
- class Person(Form):
- first_name = CharField(max_length=30)
- last_name = CharField(max_length=30)
-
- def __init__(self, name_max_length=None, *args, **kwargs):
- super(Person, self).__init__(*args, **kwargs)
-
- if name_max_length:
- self.fields['first_name'].max_length = name_max_length
- self.fields['last_name'].max_length = name_max_length
-
- f = Person(name_max_length=None)
- self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (30, 30))
- f = Person(name_max_length=20)
- self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (20, 20))
- f = Person(name_max_length=None)
- self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (30, 30))
-
- # Similarly, choices do not persist from one Form instance to the next.
- # Refs #15127.
- class Person(Form):
- first_name = CharField(required=False)
- last_name = CharField(required=False)
- gender = ChoiceField(choices=(('f', 'Female'), ('m', 'Male')))
-
- def __init__(self, allow_unspec_gender=False, *args, **kwargs):
- super(Person, self).__init__(*args, **kwargs)
-
- if allow_unspec_gender:
- self.fields['gender'].choices += (('u', 'Unspecified'),)
-
- f = Person()
- self.assertEqual(f['gender'].field.choices, [('f', 'Female'), ('m', 'Male')])
- f = Person(allow_unspec_gender=True)
- self.assertEqual(f['gender'].field.choices, [('f', 'Female'), ('m', 'Male'), ('u', 'Unspecified')])
- f = Person()
- self.assertEqual(f['gender'].field.choices, [('f', 'Female'), ('m', 'Male')])
-
- def test_validators_independence(self):
- """ Test that we are able to modify a form field validators list without polluting
- other forms """
- from django.core.validators import MaxValueValidator
- class MyForm(Form):
- myfield = CharField(max_length=25)
-
- f1 = MyForm()
- f2 = MyForm()
-
- f1.fields['myfield'].validators[0] = MaxValueValidator(12)
- self.assertFalse(f1.fields['myfield'].validators[0] == f2.fields['myfield'].validators[0])
-
- def test_hidden_widget(self):
- # HiddenInput widgets are displayed differently in the as_table(), as_ul())
- # and as_p() output of a Form -- their verbose names are not displayed, and a
- # separate row is not displayed. They're displayed in the last row of the
- # form, directly after that row's form element.
- class Person(Form):
- first_name = CharField()
- last_name = CharField()
- hidden_text = CharField(widget=HiddenInput)
- birthday = DateField()
-
- p = Person(auto_id=False)
- self.assertHTMLEqual(p.as_table(), """
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(p.as_ul(), """
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(p.as_p(), """
First name:
-
Last name:
-
Birthday:
""")
-
- # With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
- p = Person(auto_id='id_%s')
- self.assertHTMLEqual(p.as_table(), """
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(p.as_ul(), """
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(p.as_p(), """
First name:
-
Last name:
-
Birthday:
""")
-
- # If a field with a HiddenInput has errors, the as_table() and as_ul() output
- # will include the error message(s) with the text "(Hidden field [fieldname]) "
- # prepended. This message is displayed at the top of the output, regardless of
- # its field's order in the form.
- p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}, auto_id=False)
- self.assertHTMLEqual(p.as_table(), """
(Hidden field hidden_text) This field is required.
-
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(p.as_ul(), """
(Hidden field hidden_text) This field is required.
-
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(p.as_p(), """
(Hidden field hidden_text) This field is required.
-
First name:
-
Last name:
-
Birthday:
""")
-
- # A corner case: It's possible for a form to have only HiddenInputs.
- class TestForm(Form):
- foo = CharField(widget=HiddenInput)
- bar = CharField(widget=HiddenInput)
-
- p = TestForm(auto_id=False)
- self.assertHTMLEqual(p.as_table(), '')
- self.assertHTMLEqual(p.as_ul(), '')
- self.assertHTMLEqual(p.as_p(), '')
-
- def test_field_order(self):
- # A Form's fields are displayed in the same order in which they were defined.
- class TestForm(Form):
- field1 = CharField()
- field2 = CharField()
- field3 = CharField()
- field4 = CharField()
- field5 = CharField()
- field6 = CharField()
- field7 = CharField()
- field8 = CharField()
- field9 = CharField()
- field10 = CharField()
- field11 = CharField()
- field12 = CharField()
- field13 = CharField()
- field14 = CharField()
-
- p = TestForm(auto_id=False)
- self.assertHTMLEqual(p.as_table(), """
Field1:
-
Field2:
-
Field3:
-
Field4:
-
Field5:
-
Field6:
-
Field7:
-
Field8:
-
Field9:
-
Field10:
-
Field11:
-
Field12:
-
Field13:
-
Field14:
""")
-
- def test_form_html_attributes(self):
- # Some Field classes have an effect on the HTML attributes of their associated
- # Widget. If you set max_length in a CharField and its associated widget is
- # either a TextInput or PasswordInput, then the widget's rendered HTML will
- # include the "maxlength" attribute.
- class UserRegistration(Form):
- username = CharField(max_length=10) # uses TextInput by default
- password = CharField(max_length=10, widget=PasswordInput)
- realname = CharField(max_length=10, widget=TextInput) # redundantly define widget, just to test
- address = CharField() # no max_length defined here
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
-
Realname:
-
Address:
""")
-
- # If you specify a custom "attrs" that includes the "maxlength" attribute,
- # the Field's max_length attribute will override whatever "maxlength" you specify
- # in "attrs".
- class UserRegistration(Form):
- username = CharField(max_length=10, widget=TextInput(attrs={'maxlength': 20}))
- password = CharField(max_length=10, widget=PasswordInput)
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
-
- def test_specifying_labels(self):
- # You can specify the label for a field by using the 'label' argument to a Field
- # class. If you don't specify 'label', Django will use the field name with
- # underscores converted to spaces, and the initial letter capitalized.
- class UserRegistration(Form):
- username = CharField(max_length=10, label='Your username')
- password1 = CharField(widget=PasswordInput)
- password2 = CharField(widget=PasswordInput, label='Contraseña (de nuevo)')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Your username:
-
Password1:
-
Contraseña (de nuevo):
""")
-
- # Labels for as_* methods will only end in a colon if they don't end in other
- # punctuation already.
- class Questions(Form):
- q1 = CharField(label='The first question')
- q2 = CharField(label='What is your name?')
- q3 = CharField(label='The answer to life is:')
- q4 = CharField(label='Answer this question!')
- q5 = CharField(label='The last question. Period.')
-
- self.assertHTMLEqual(Questions(auto_id=False).as_p(), """
""")
-
- # If a label is set to the empty string for a field, that field won't get a label.
- class UserRegistration(Form):
- username = CharField(max_length=10, label='')
- password = CharField(widget=PasswordInput)
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
-
Password:
""")
- p = UserRegistration(auto_id='id_%s')
- self.assertHTMLEqual(p.as_ul(), """
-
Password:
""")
-
- # If label is None, Django will auto-create the label from the field name. This
- # is default behavior.
- class UserRegistration(Form):
- username = CharField(max_length=10, label=None)
- password = CharField(widget=PasswordInput)
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
- p = UserRegistration(auto_id='id_%s')
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
-
- def test_label_suffix(self):
- # You can specify the 'label_suffix' argument to a Form class to modify the
- # punctuation symbol used at the end of a label. By default, the colon (:) is
- # used, and is only appended to the label if the label doesn't already end with a
- # punctuation symbol: ., !, ? or :. If you specify a different suffix, it will
- # be appended regardless of the last character of the label.
- class FavoriteForm(Form):
- color = CharField(label='Favorite color?')
- animal = CharField(label='Favorite animal')
-
- f = FavoriteForm(auto_id=False)
- self.assertHTMLEqual(f.as_ul(), """
Favorite color?
-
Favorite animal:
""")
- f = FavoriteForm(auto_id=False, label_suffix='?')
- self.assertHTMLEqual(f.as_ul(), """
Favorite color?
-
Favorite animal?
""")
- f = FavoriteForm(auto_id=False, label_suffix='')
- self.assertHTMLEqual(f.as_ul(), """
Favorite color?
-
Favorite animal
""")
- f = FavoriteForm(auto_id=False, label_suffix='\u2192')
- self.assertHTMLEqual(f.as_ul(), '
Favorite color?
\n
Favorite animal\u2192
')
-
- def test_initial_data(self):
- # You can specify initial data for a field by using the 'initial' argument to a
- # Field class. This initial data is displayed when a Form is rendered with *no*
- # data. It is not displayed when a Form is rendered with any data (including an
- # empty dictionary). Also, the initial value is *not* used if data for a
- # particular required field isn't provided.
- class UserRegistration(Form):
- username = CharField(max_length=10, initial='django')
- password = CharField(widget=PasswordInput)
-
- # Here, we're not submitting any data, so the initial value will be displayed.)
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
-
- # Here, we're submitting data, so the initial value will *not* be displayed.
- p = UserRegistration({}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
This field is required.
Username:
-
This field is required.
Password:
""")
- p = UserRegistration({'username': ''}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
This field is required.
Username:
-
This field is required.
Password:
""")
- p = UserRegistration({'username': 'foo'}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
This field is required.
Password:
""")
-
- # An 'initial' value is *not* used as a fallback if data is not provided. In this
- # example, we don't provide a value for 'username', and the form raises a
- # validation error rather than using the initial value for 'username'.
- p = UserRegistration({'password': 'secret'})
- self.assertEqual(p.errors['username'], ['This field is required.'])
- self.assertFalse(p.is_valid())
-
- def test_dynamic_initial_data(self):
- # The previous technique dealt with "hard-coded" initial data, but it's also
- # possible to specify initial data after you've already created the Form class
- # (i.e., at runtime). Use the 'initial' parameter to the Form constructor. This
- # should be a dictionary containing initial values for one or more fields in the
- # form, keyed by field name.
- class UserRegistration(Form):
- username = CharField(max_length=10)
- password = CharField(widget=PasswordInput)
-
- # Here, we're not submitting any data, so the initial value will be displayed.)
- p = UserRegistration(initial={'username': 'django'}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
- p = UserRegistration(initial={'username': 'stephane'}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
-
- # The 'initial' parameter is meaningless if you pass data.
- p = UserRegistration({}, initial={'username': 'django'}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
""")
-
- # A dynamic 'initial' value is *not* used as a fallback if data is not provided.
- # In this example, we don't provide a value for 'username', and the form raises a
- # validation error rather than using the initial value for 'username'.
- p = UserRegistration({'password': 'secret'}, initial={'username': 'django'})
- self.assertEqual(p.errors['username'], ['This field is required.'])
- self.assertFalse(p.is_valid())
-
- # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
- # then the latter will get precedence.
- class UserRegistration(Form):
- username = CharField(max_length=10, initial='django')
- password = CharField(widget=PasswordInput)
-
- p = UserRegistration(initial={'username': 'babik'}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
""")
-
- def test_callable_initial_data(self):
- # The previous technique dealt with raw values as initial data, but it's also
- # possible to specify callable data.
- class UserRegistration(Form):
- username = CharField(max_length=10)
- password = CharField(widget=PasswordInput)
- options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')])
-
- # We need to define functions that get called later.)
- def initial_django():
- return 'django'
-
- def initial_stephane():
- return 'stephane'
-
- def initial_options():
- return ['f','b']
-
- def initial_other_options():
- return ['b','w']
-
- # Here, we're not submitting any data, so the initial value will be displayed.)
- p = UserRegistration(initial={'username': initial_django, 'options': initial_options}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username:
-
Password:
-
Options:
""")
-
- # The 'initial' parameter is meaningless if you pass data.
- p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
""")
-
- # A callable 'initial' value is *not* used as a fallback if data is not provided.
- # In this example, we don't provide a value for 'username', and the form raises a
- # validation error rather than using the initial value for 'username'.
- p = UserRegistration({'password': 'secret'}, initial={'username': initial_django, 'options': initial_options})
- self.assertEqual(p.errors['username'], ['This field is required.'])
- self.assertFalse(p.is_valid())
-
- # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
- # then the latter will get precedence.
- class UserRegistration(Form):
- username = CharField(max_length=10, initial=initial_django)
- password = CharField(widget=PasswordInput)
- options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')], initial=initial_other_options)
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
""")
-
- def test_changed_data(self):
- class Person(Form):
- first_name = CharField(initial='Hans')
- last_name = CharField(initial='Greatel')
- birthday = DateField(initial=datetime.date(1974, 8, 16))
-
- p = Person(data={'first_name': 'Hans', 'last_name': 'Scrmbl',
- 'birthday': '1974-08-16'})
- self.assertTrue(p.is_valid())
- self.assertNotIn('first_name', p.changed_data)
- self.assertIn('last_name', p.changed_data)
- self.assertNotIn('birthday', p.changed_data)
-
- # Test that field raising ValidationError is always in changed_data
- class PedanticField(forms.Field):
- def to_python(self, value):
- raise ValidationError('Whatever')
-
- class Person2(Person):
- pedantic = PedanticField(initial='whatever', show_hidden_initial=True)
-
- p = Person2(data={'first_name': 'Hans', 'last_name': 'Scrmbl',
- 'birthday': '1974-08-16', 'initial-pedantic': 'whatever'})
- self.assertFalse(p.is_valid())
- self.assertIn('pedantic', p.changed_data)
-
- def test_boundfield_values(self):
- # It's possible to get to the value which would be used for rendering
- # the widget for a field by using the BoundField's value method.
-
- class UserRegistration(Form):
- username = CharField(max_length=10, initial='djangonaut')
- password = CharField(widget=PasswordInput)
-
- unbound = UserRegistration()
- bound = UserRegistration({'password': 'foo'})
- self.assertEqual(bound['username'].value(), None)
- self.assertEqual(unbound['username'].value(), 'djangonaut')
- self.assertEqual(bound['password'].value(), 'foo')
- self.assertEqual(unbound['password'].value(), None)
-
- def test_help_text(self):
- # You can specify descriptive text for a field by using the 'help_text' argument)
- class UserRegistration(Form):
- username = CharField(max_length=10, help_text='e.g., user@example.com')
- password = CharField(widget=PasswordInput, help_text='Wählen Sie mit Bedacht.')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
-
Password: Wählen Sie mit Bedacht.
""")
- self.assertHTMLEqual(p.as_p(), """
Username: e.g., user@example.com
-
Password: Wählen Sie mit Bedacht.
""")
- self.assertHTMLEqual(p.as_table(), """
Username:
e.g., user@example.com
-
Password:
Wählen Sie mit Bedacht.
""")
-
- # The help text is displayed whether or not data is provided for the form.
- p = UserRegistration({'username': 'foo'}, auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
-
This field is required.
Password: Wählen Sie mit Bedacht.
""")
-
- # help_text is not displayed for hidden fields. It can be used for documentation
- # purposes, though.
- class UserRegistration(Form):
- username = CharField(max_length=10, help_text='e.g., user@example.com')
- password = CharField(widget=PasswordInput)
- next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
-
Password:
""")
-
- def test_subclassing_forms(self):
- # You can subclass a Form to add fields. The resulting form subclass will have
- # all of the fields of the parent Form, plus whichever fields you define in the
- # subclass.
- class Person(Form):
- first_name = CharField()
- last_name = CharField()
- birthday = DateField()
-
- class Musician(Person):
- instrument = CharField()
-
- p = Person(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
First name:
-
Last name:
-
Birthday:
""")
- m = Musician(auto_id=False)
- self.assertHTMLEqual(m.as_ul(), """
First name:
-
Last name:
-
Birthday:
-
Instrument:
""")
-
- # Yes, you can subclass multiple forms. The fields are added in the order in
- # which the parent classes are listed.
- class Person(Form):
- first_name = CharField()
- last_name = CharField()
- birthday = DateField()
-
- class Instrument(Form):
- instrument = CharField()
-
- class Beatle(Person, Instrument):
- haircut_type = CharField()
-
- b = Beatle(auto_id=False)
- self.assertHTMLEqual(b.as_ul(), """
First name:
-
Last name:
-
Birthday:
-
Instrument:
-
Haircut type:
""")
-
- def test_forms_with_prefixes(self):
- # Sometimes it's necessary to have multiple forms display on the same HTML page,
- # or multiple copies of the same form. We can accomplish this with form prefixes.
- # Pass the keyword argument 'prefix' to the Form constructor to use this feature.
- # This value will be prepended to each HTML form field name. One way to think
- # about this is "namespaces for HTML forms". Notice that in the data argument,
- # each field's key has the prefix, in this case 'person1', prepended to the
- # actual field name.
- class Person(Form):
- first_name = CharField()
- last_name = CharField()
- birthday = DateField()
-
- data = {
- 'person1-first_name': 'John',
- 'person1-last_name': 'Lennon',
- 'person1-birthday': '1940-10-9'
- }
- p = Person(data, prefix='person1')
- self.assertHTMLEqual(p.as_ul(), """
First name:
-
Last name:
-
Birthday:
""")
- self.assertHTMLEqual(str(p['first_name']), '')
- self.assertHTMLEqual(str(p['last_name']), '')
- self.assertHTMLEqual(str(p['birthday']), '')
- self.assertEqual(p.errors, {})
- self.assertTrue(p.is_valid())
- self.assertEqual(p.cleaned_data['first_name'], 'John')
- self.assertEqual(p.cleaned_data['last_name'], 'Lennon')
- self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9))
-
- # Let's try submitting some bad data to make sure form.errors and field.errors
- # work as expected.
- data = {
- 'person1-first_name': '',
- 'person1-last_name': '',
- 'person1-birthday': ''
- }
- p = Person(data, prefix='person1')
- self.assertEqual(p.errors['first_name'], ['This field is required.'])
- self.assertEqual(p.errors['last_name'], ['This field is required.'])
- self.assertEqual(p.errors['birthday'], ['This field is required.'])
- self.assertEqual(p['first_name'].errors, ['This field is required.'])
- try:
- p['person1-first_name'].errors
- self.fail('Attempts to access non-existent fields should fail.')
- except KeyError:
- pass
-
- # In this example, the data doesn't have a prefix, but the form requires it, so
- # the form doesn't "see" the fields.
- data = {
- 'first_name': 'John',
- 'last_name': 'Lennon',
- 'birthday': '1940-10-9'
- }
- p = Person(data, prefix='person1')
- self.assertEqual(p.errors['first_name'], ['This field is required.'])
- self.assertEqual(p.errors['last_name'], ['This field is required.'])
- self.assertEqual(p.errors['birthday'], ['This field is required.'])
-
- # With prefixes, a single data dictionary can hold data for multiple instances
- # of the same form.
- data = {
- 'person1-first_name': 'John',
- 'person1-last_name': 'Lennon',
- 'person1-birthday': '1940-10-9',
- 'person2-first_name': 'Jim',
- 'person2-last_name': 'Morrison',
- 'person2-birthday': '1943-12-8'
- }
- p1 = Person(data, prefix='person1')
- self.assertTrue(p1.is_valid())
- self.assertEqual(p1.cleaned_data['first_name'], 'John')
- self.assertEqual(p1.cleaned_data['last_name'], 'Lennon')
- self.assertEqual(p1.cleaned_data['birthday'], datetime.date(1940, 10, 9))
- p2 = Person(data, prefix='person2')
- self.assertTrue(p2.is_valid())
- self.assertEqual(p2.cleaned_data['first_name'], 'Jim')
- self.assertEqual(p2.cleaned_data['last_name'], 'Morrison')
- self.assertEqual(p2.cleaned_data['birthday'], datetime.date(1943, 12, 8))
-
- # By default, forms append a hyphen between the prefix and the field name, but a
- # form can alter that behavior by implementing the add_prefix() method. This
- # method takes a field name and returns the prefixed field, according to
- # self.prefix.
- class Person(Form):
- first_name = CharField()
- last_name = CharField()
- birthday = DateField()
-
- def add_prefix(self, field_name):
- return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name
-
- p = Person(prefix='foo')
- self.assertHTMLEqual(p.as_ul(), """
First name:
-
Last name:
-
Birthday:
""")
- data = {
- 'foo-prefix-first_name': 'John',
- 'foo-prefix-last_name': 'Lennon',
- 'foo-prefix-birthday': '1940-10-9'
- }
- p = Person(data, prefix='foo')
- self.assertTrue(p.is_valid())
- self.assertEqual(p.cleaned_data['first_name'], 'John')
- self.assertEqual(p.cleaned_data['last_name'], 'Lennon')
- self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9))
-
- def test_forms_with_null_boolean(self):
- # NullBooleanField is a bit of a special case because its presentation (widget)
- # is different than its data. This is handled transparently, though.
- class Person(Form):
- name = CharField()
- is_cool = NullBooleanField()
-
- p = Person({'name': 'Joe'}, auto_id=False)
- self.assertHTMLEqual(str(p['is_cool']), """""")
- p = Person({'name': 'Joe', 'is_cool': '1'}, auto_id=False)
- self.assertHTMLEqual(str(p['is_cool']), """""")
- p = Person({'name': 'Joe', 'is_cool': '2'}, auto_id=False)
- self.assertHTMLEqual(str(p['is_cool']), """""")
- p = Person({'name': 'Joe', 'is_cool': '3'}, auto_id=False)
- self.assertHTMLEqual(str(p['is_cool']), """""")
- p = Person({'name': 'Joe', 'is_cool': True}, auto_id=False)
- self.assertHTMLEqual(str(p['is_cool']), """""")
- p = Person({'name': 'Joe', 'is_cool': False}, auto_id=False)
- self.assertHTMLEqual(str(p['is_cool']), """""")
-
- def test_forms_with_file_fields(self):
- # FileFields are a special case because they take their data from the request.FILES,
- # not request.POST.
- class FileForm(Form):
- file1 = FileField()
-
- f = FileForm(auto_id=False)
- self.assertHTMLEqual(f.as_table(), '
')
- self.assertTrue(f.is_valid())
-
- f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False)
- self.assertHTMLEqual(f.as_table(), '
File1:
')
-
- def test_basic_processing_in_view(self):
- class UserRegistration(Form):
- username = CharField(max_length=10)
- password1 = CharField(widget=PasswordInput)
- password2 = CharField(widget=PasswordInput)
-
- def clean(self):
- if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
- raise ValidationError('Please make sure your passwords match.')
-
- return self.cleaned_data
-
- def my_function(method, post_data):
- if method == 'POST':
- form = UserRegistration(post_data, auto_id=False)
- else:
- form = UserRegistration(auto_id=False)
-
- if form.is_valid():
- return 'VALID: %r' % sorted(six.iteritems(form.cleaned_data))
-
- t = Template('')
- return t.render(Context({'form': form}))
-
- # Case 1: GET (an empty form, with no errors).)
- self.assertHTMLEqual(my_function('GET', {}), """""")
- # Case 2: POST with erroneous data (a redisplayed form, with errors).)
- self.assertHTMLEqual(my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'}), """""")
- # Case 3: POST with valid data (the success message).)
- self.assertEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}),
- str_prefix("VALID: [('password1', %(_)s'secret'), ('password2', %(_)s'secret'), ('username', %(_)s'adrian')]"))
-
- def test_templates_with_forms(self):
- class UserRegistration(Form):
- username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.")
- password1 = CharField(widget=PasswordInput)
- password2 = CharField(widget=PasswordInput)
-
- def clean(self):
- if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
- raise ValidationError('Please make sure your passwords match.')
-
- return self.cleaned_data
-
- # You have full flexibility in displaying form fields in a template. Just pass a
- # Form instance to the template, and use "dot" access to refer to individual
- # fields. Note, however, that this flexibility comes with the responsibility of
- # displaying all the errors, including any that might not be associated with a
- # particular field.
- t = Template('''''')
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)})), """""")
-
- # Use form.[field].label to output a field's label. You can specify the label for
- # a field by using the 'label' argument to a Field class. If you don't specify
- # 'label', Django will use the field name with underscores converted to spaces,
- # and the initial letter capitalized.
- t = Template('''''')
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
-
- # User form.[field].label_tag to output a field's label with a tag
- # wrapped around it, but *only* if the given field has an "id" attribute.
- # Recall from above that passing the "auto_id" argument to a Form gives each
- # field an "id" attribute.
- t = Template('''''')
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id='id_%s')})), """""")
-
- # User form.[field].help_text to output a field's help text. If the given field
- # does not have help text, nothing will be output.
- t = Template('''''')
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
- self.assertEqual(Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})), '')
-
- # To display the errors that aren't associated with a particular field -- e.g.,
- # the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the
- # template. If used on its own, it is displayed as a
(or an empty string, if
- # the list of errors is empty). You can also use it in {% if %} statements.
- t = Template('''
''')
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})), """""")
- t = Template('''''')
- self.assertHTMLEqual(t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})), """""")
-
- def test_empty_permitted(self):
- # Sometimes (pretty much in formsets) we want to allow a form to pass validation
- # if it is completely empty. We can accomplish this by using the empty_permitted
- # agrument to a form constructor.
- class SongForm(Form):
- artist = CharField()
- name = CharField()
-
- # First let's show what happens id empty_permitted=False (the default):
- data = {'artist': '', 'song': ''}
- form = SongForm(data, empty_permitted=False)
- self.assertFalse(form.is_valid())
- self.assertEqual(form.errors, {'name': ['This field is required.'], 'artist': ['This field is required.']})
- self.assertEqual(form.cleaned_data, {})
-
- # Now let's show what happens when empty_permitted=True and the form is empty.
- form = SongForm(data, empty_permitted=True)
- self.assertTrue(form.is_valid())
- self.assertEqual(form.errors, {})
- self.assertEqual(form.cleaned_data, {})
-
- # But if we fill in data for one of the fields, the form is no longer empty and
- # the whole thing must pass validation.
- data = {'artist': 'The Doors', 'song': ''}
- form = SongForm(data, empty_permitted=False)
- self.assertFalse(form.is_valid())
- self.assertEqual(form.errors, {'name': ['This field is required.']})
- self.assertEqual(form.cleaned_data, {'artist': 'The Doors'})
-
- # If a field is not given in the data then None is returned for its data. Lets
- # make sure that when checking for empty_permitted that None is treated
- # accordingly.
- data = {'artist': None, 'song': ''}
- form = SongForm(data, empty_permitted=True)
- self.assertTrue(form.is_valid())
-
- # However, we *really* need to be sure we are checking for None as any data in
- # initial that returns False on a boolean call needs to be treated literally.
- class PriceForm(Form):
- amount = FloatField()
- qty = IntegerField()
-
- data = {'amount': '0.0', 'qty': ''}
- form = PriceForm(data, initial={'amount': 0.0}, empty_permitted=True)
- self.assertTrue(form.is_valid())
-
- def test_extracting_hidden_and_visible(self):
- class SongForm(Form):
- token = CharField(widget=HiddenInput)
- artist = CharField()
- name = CharField()
-
- form = SongForm()
- self.assertEqual([f.name for f in form.hidden_fields()], ['token'])
- self.assertEqual([f.name for f in form.visible_fields()], ['artist', 'name'])
-
- def test_hidden_initial_gets_id(self):
- class MyForm(Form):
- field1 = CharField(max_length=50, show_hidden_initial=True)
-
- self.assertHTMLEqual(MyForm().as_table(), '
""")
-
- def test_label_split_datetime_not_displayed(self):
- class EventForm(Form):
- happened_at = SplitDateTimeField(widget=widgets.SplitHiddenDateTimeWidget)
-
- form = EventForm()
- self.assertHTMLEqual(form.as_ul(), '')
-
- def test_multivalue_field_validation(self):
- def bad_names(value):
- if value == 'bad value':
- raise ValidationError('bad value not allowed')
-
- class NameField(MultiValueField):
- def __init__(self, fields=(), *args, **kwargs):
- fields = (CharField(label='First name', max_length=10),
- CharField(label='Last name', max_length=10))
- super(NameField, self).__init__(fields=fields, *args, **kwargs)
-
- def compress(self, data_list):
- return ' '.join(data_list)
-
- class NameForm(Form):
- name = NameField(validators=[bad_names])
-
- form = NameForm(data={'name' : ['bad', 'value']})
- form.full_clean()
- self.assertFalse(form.is_valid())
- self.assertEqual(form.errors, {'name': ['bad value not allowed']})
- form = NameForm(data={'name' : ['should be overly', 'long for the field names']})
- self.assertFalse(form.is_valid())
- self.assertEqual(form.errors, {'name': ['Ensure this value has at most 10 characters (it has 16).',
- 'Ensure this value has at most 10 characters (it has 24).']})
- form = NameForm(data={'name' : ['fname', 'lname']})
- self.assertTrue(form.is_valid())
- self.assertEqual(form.cleaned_data, {'name' : 'fname lname'})
-
- def test_custom_empty_values(self):
- """
- Test that form fields can customize what is considered as an empty value
- for themselves (#19997).
- """
- class CustomJSONField(CharField):
- empty_values = [None, '']
- def to_python(self, value):
- # Fake json.loads
- if value == '{}':
- return {}
- return super(CustomJSONField, self).to_python(value)
-
- class JSONForm(forms.Form):
- json = CustomJSONField()
-
- form = JSONForm(data={'json': '{}'});
- form.full_clean()
- self.assertEqual(form.cleaned_data, {'json' : {}})
-
- def test_boundfield_label_tag(self):
- class SomeForm(Form):
- field = CharField()
- boundfield = SomeForm()['field']
-
- testcases = [ # (args, kwargs, expected)
- # without anything: just print the
- ((), {}, 'Field'),
-
- # passing just one argument: overrides the field's label
- (('custom',), {}, 'custom'),
-
- # the overriden label is escaped
- (('custom&',), {}, 'custom&'),
- ((mark_safe('custom&'),), {}, 'custom&'),
-
- # Passing attrs to add extra attributes on the
- ((), {'attrs': {'class': 'pretty'}}, 'Field')
- ]
-
- for args, kwargs, expected in testcases:
- self.assertHTMLEqual(boundfield.label_tag(*args, **kwargs), expected)
-
- def test_boundfield_label_tag_no_id(self):
- """
- If a widget has no id, label_tag just returns the text with no
- surrounding .
- """
- class SomeForm(Form):
- field = CharField()
- boundfield = SomeForm(auto_id='')['field']
-
- self.assertHTMLEqual(boundfield.label_tag(), 'Field')
- self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&')
diff --git a/tests/forms_tests/tests/formsets.py b/tests/forms_tests/tests/formsets.py
deleted file mode 100644
index 4ac3c5ecf1..0000000000
--- a/tests/forms_tests/tests/formsets.py
+++ /dev/null
@@ -1,1091 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.forms import (CharField, DateField, FileField, Form, IntegerField,
- ValidationError, formsets)
-from django.forms.formsets import BaseFormSet, formset_factory
-from django.forms.util import ErrorList
-from django.test 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'])
-
-
-class EmptyFsetWontValidate(BaseFormSet):
- def clean(self):
- raise ValidationError("Clean method called")
-
-
-# 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.assertHTMLEqual(str(formset), """
-
Choice:
-
Votes:
""")
-
- # 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': 'Calexico'}])
-
- # If a FormSet was not passed any data, its is_valid and has_changed
- # methods should return False.
- formset = ChoiceFormSet()
- self.assertFalse(formset.is_valid())
- self.assertFalse(formset.has_changed())
-
- 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': ['This field is required.']}])
-
- def test_formset_has_changed(self):
- # FormSet instances has_changed method will be True if any data is
- # passed to his forms, even if the formset didn't validate
- 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': '',
- 'choices-0-votes': '',
- }
- blank_formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
- self.assertFalse(blank_formset.has_changed())
-
- # invalid formset test
- data['choices-0-choice'] = 'Calexico'
- invalid_formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
- self.assertFalse(invalid_formset.is_valid())
- self.assertTrue(invalid_formset.has_changed())
-
- # valid formset test
- data['choices-0-votes'] = '100'
- valid_formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
- self.assertTrue(valid_formset.is_valid())
- self.assertTrue(valid_formset.has_changed())
-
- 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': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
-
Votes:
-
Choice:
-
Votes:
""")
-
- # 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': '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': ['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': ['This field is required.'], 'choice': ['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.assertHTMLEqual('\n'.join(form_output), """
Choice:
-
Votes:
-
Choice:
-
Votes:
-
Choice:
-
Votes:
""")
-
- # 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': 'Calexico'}, {}, {}])
-
- def test_formset_validate_max_flag(self):
- # If validate_max is set and max_num is less than TOTAL_FORMS in the
- # data, then throw an exception. MAX_NUM_FORMS in the data is
- # irrelevant here (it's output as a hint for the client but its
- # value in the returned data is not checked)
-
- data = {
- 'choices-TOTAL_FORMS': '2', # the number of forms rendered
- 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
- 'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
- 'choices-0-choice': 'Zero',
- 'choices-0-votes': '0',
- 'choices-1-choice': 'One',
- 'choices-1-votes': '1',
- }
-
- ChoiceFormSet = formset_factory(Choice, extra=1, max_num=1, validate_max=True)
- formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
- self.assertFalse(formset.is_valid())
- self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.'])
-
- 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': ['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': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
-
Votes:
-
Choice:
-
Votes:
-
Choice:
-
Votes:
-
Choice:
-
Votes:
""")
-
- # Make sure retrieving an empty form works, and it shows up in the form list
-
- self.assertTrue(formset.empty_form.empty_permitted)
- self.assertHTMLEqual(formset.empty_form.as_ul(), """
Choice:
-
Votes:
""")
-
- 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': 'Calexico', 'votes': 100}, {'choice': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
-
Votes:
-
Delete:
-
Choice:
-
Votes:
-
Delete:
-
Choice:
-
Votes:
-
Delete:
""")
-
- # 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': 'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': 'Fergie'}, {}])
- self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'choice': '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': '', 'form-0-DELETE': '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': 'Calexico', 'votes': 100}, {'choice': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
-
Votes:
-
Order:
-
Choice:
-
Votes:
-
Order:
-
Choice:
-
Votes:
-
Order:
""")
-
- 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': 'The Decemberists'},
- {'votes': 100, 'ORDER': 1, 'choice': 'Calexico'},
- {'votes': 900, 'ORDER': 2, 'choice': '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': 'Calexico'},
- {'votes': 900, 'ORDER': 2, 'choice': 'Fergie'},
- {'votes': 500, 'ORDER': None, 'choice': 'The Decemberists'},
- {'votes': 50, 'ORDER': None, 'choice': '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': 'Calexico', 'votes': 100},
- {'choice': 'Fergie', 'votes': 900},
- {'choice': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
-
Votes:
-
Order:
-
Delete:
-
Choice:
-
Votes:
-
Order:
-
Delete:
-
Choice:
-
Votes:
-
Order:
-
Delete:
-
Choice:
-
Votes:
-
Order:
-
Delete:
""")
-
- # 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': 'The Decemberists'},
- {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': 'Calexico'},
- ])
- self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': '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': '',
- 'form-0-DELETE': '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 a high default value, leaving the
- # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
-
Name:
-
Name:
""")
-
- # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
-
Name:
""")
-
- # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
""")
-
- def test_max_num_with_initial_data(self):
- # max_num with initial data
-
- # When not passed, max_num will take a high default value, leaving the
- # number of forms only controlled by the value 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.assertHTMLEqual('\n'.join(form_output), """
Name:
-
Name:
""")
-
- def test_max_num_zero(self):
- # If max_num is 0 then no form is rendered at all, regardless of extra,
- # unless initial data is present. (This changed in the patch for bug
- # 20084 -- previously max_num=0 trumped initial data)
-
- LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
- formset = LimitedFavoriteDrinkFormSet()
- form_output = []
-
- for form in formset.forms:
- form_output.append(str(form))
-
- self.assertEqual('\n'.join(form_output), "")
-
- # test that initial trumps max_num
-
- 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), """
Name:
-
Name:
""")
-
- def test_more_initial_than_max_num(self):
- # More initial forms than max_num now results in all initial forms
- # being displayed (but no extra forms). This behavior was changed
- # from max_num taking precedence in the patch for #20084
-
- 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.assertHTMLEqual('\n'.join(form_output), """
Name:
-
Name:
-
Name:
""")
-
- # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
-
Name:
""")
-
- 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')
-
- data = {
- 'form-TOTAL_FORMS': '2',
- 'form-INITIAL_FORMS': '0',
- 'form-MAX_NUM_FORMS': '0',
- }
- formset = FavoriteDrinksFormSet(data=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(), ['You may only specify a drink once.'])
-
- def test_formset_iteration(self):
- # Regression tests for #16455 -- formset instances are iterable
- ChoiceFormset = formset_factory(Choice, extra=3)
- formset = ChoiceFormset()
-
- # confirm iterated formset yields formset.forms
- forms = list(formset)
- self.assertEqual(forms, formset.forms)
- self.assertEqual(len(formset), len(forms))
-
- # confirm indexing of formset
- self.assertEqual(formset[0], forms[0])
- try:
- formset[3]
- self.fail('Requesting an invalid formset index should raise an exception')
- except IndexError:
- pass
-
- # Formets can override the default iteration order
- class BaseReverseFormSet(BaseFormSet):
- def __iter__(self):
- return reversed(self.forms)
-
- def __getitem__(self, idx):
- return super(BaseReverseFormSet, self).__getitem__(len(self) - idx - 1)
-
- ReverseChoiceFormset = formset_factory(Choice, BaseReverseFormSet, extra=3)
- reverse_formset = ReverseChoiceFormset()
-
- # confirm that __iter__ modifies rendering order
- # compare forms from "reverse" formset with forms from original formset
- self.assertEqual(str(reverse_formset[0]), str(forms[-1]))
- self.assertEqual(str(reverse_formset[1]), str(forms[-2]))
- self.assertEqual(len(reverse_formset), len(forms))
-
- def test_formset_nonzero(self):
- """
- Formsets with no forms should still evaluate as true.
- Regression test for #15722
- """
- ChoiceFormset = formset_factory(Choice, extra=0)
- formset = ChoiceFormset()
- self.assertEqual(len(formset.forms), 0)
- self.assertTrue(formset)
-
-
- def test_formset_error_class(self):
- # Regression tests for #16479 -- formsets form use ErrorList instead of supplied error_class
- class CustomErrorList(ErrorList):
- pass
-
- formset = FavoriteDrinksFormSet(error_class=CustomErrorList)
- self.assertEqual(formset.forms[0].error_class, CustomErrorList)
-
- def test_formset_calls_forms_is_valid(self):
- # Regression tests for #18574 -- make sure formsets call
- # is_valid() on each form.
-
- class AnotherChoice(Choice):
- def is_valid(self):
- self.is_valid_called = True
- return super(AnotherChoice, self).is_valid()
-
- AnotherChoiceFormSet = formset_factory(AnotherChoice)
- data = {
- 'choices-TOTAL_FORMS': '1', # number of forms rendered
- 'choices-INITIAL_FORMS': '0', # number of forms with initial data
- 'choices-MAX_NUM_FORMS': '0', # max number of forms
- 'choices-0-choice': 'Calexico',
- 'choices-0-votes': '100',
- }
- formset = AnotherChoiceFormSet(data, auto_id=False, prefix='choices')
- self.assertTrue(formset.is_valid())
- self.assertTrue(all([form.is_valid_called for form in formset.forms]))
-
- def test_hard_limit_on_instantiated_forms(self):
- """A formset has a hard limit on the number of forms instantiated."""
- # reduce the default limit of 1000 temporarily for testing
- _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM
- try:
- formsets.DEFAULT_MAX_NUM = 2
- ChoiceFormSet = formset_factory(Choice, max_num=1)
- # someone fiddles with the mgmt form data...
- formset = ChoiceFormSet(
- {
- 'choices-TOTAL_FORMS': '4',
- 'choices-INITIAL_FORMS': '0',
- 'choices-MAX_NUM_FORMS': '4',
- 'choices-0-choice': 'Zero',
- 'choices-0-votes': '0',
- 'choices-1-choice': 'One',
- 'choices-1-votes': '1',
- 'choices-2-choice': 'Two',
- 'choices-2-votes': '2',
- 'choices-3-choice': 'Three',
- 'choices-3-votes': '3',
- },
- prefix='choices',
- )
- # But we still only instantiate 3 forms
- self.assertEqual(len(formset.forms), 3)
- # and the formset isn't valid
- self.assertFalse(formset.is_valid())
- finally:
- formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM
-
- def test_increase_hard_limit(self):
- """Can increase the built-in forms limit via a higher max_num."""
- # reduce the default limit of 1000 temporarily for testing
- _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM
- try:
- formsets.DEFAULT_MAX_NUM = 3
- # for this form, we want a limit of 4
- ChoiceFormSet = formset_factory(Choice, max_num=4)
- formset = ChoiceFormSet(
- {
- 'choices-TOTAL_FORMS': '4',
- 'choices-INITIAL_FORMS': '0',
- 'choices-MAX_NUM_FORMS': '4',
- 'choices-0-choice': 'Zero',
- 'choices-0-votes': '0',
- 'choices-1-choice': 'One',
- 'choices-1-votes': '1',
- 'choices-2-choice': 'Two',
- 'choices-2-votes': '2',
- 'choices-3-choice': 'Three',
- 'choices-3-votes': '3',
- },
- prefix='choices',
- )
- # Four forms are instantiated and no exception is raised
- self.assertEqual(len(formset.forms), 4)
- finally:
- formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM
-
-
-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.assertHTMLEqual(formset.as_table(),"""
-
""")
-
-
-# Regression test for #11418 #################################################
-class ArticleForm(Form):
- title = CharField()
- pub_date = DateField()
-
-ArticleFormSet = formset_factory(ArticleForm)
-
-class TestIsBoundBehavior(TestCase):
- def test_no_data_raises_validation_error(self):
- self.assertRaises(ValidationError, ArticleFormSet, {})
-
- def test_with_management_data_attrs_work_fine(self):
- data = {
- 'form-TOTAL_FORMS': '1',
- 'form-INITIAL_FORMS': '0',
- }
- formset = ArticleFormSet(data)
- self.assertEqual(0, formset.initial_form_count())
- self.assertEqual(1, formset.total_form_count())
- self.assertTrue(formset.is_bound)
- self.assertTrue(formset.forms[0].is_bound)
- self.assertTrue(formset.is_valid())
- self.assertTrue(formset.forms[0].is_valid())
- self.assertEqual([{}], formset.cleaned_data)
-
-
- def test_form_errors_are_cought_by_formset(self):
- data = {
- 'form-TOTAL_FORMS': '2',
- 'form-INITIAL_FORMS': '0',
- 'form-0-title': 'Test',
- 'form-0-pub_date': '1904-06-16',
- 'form-1-title': 'Test',
- 'form-1-pub_date': '', # <-- this date is missing but required
- }
- formset = ArticleFormSet(data)
- self.assertFalse(formset.is_valid())
- self.assertEqual([{}, {'pub_date': ['This field is required.']}], formset.errors)
-
- def test_empty_forms_are_unbound(self):
- data = {
- 'form-TOTAL_FORMS': '1',
- 'form-INITIAL_FORMS': '0',
- 'form-0-title': 'Test',
- 'form-0-pub_date': '1904-06-16',
- }
- unbound_formset = ArticleFormSet()
- bound_formset = ArticleFormSet(data)
-
- empty_forms = []
-
- empty_forms.append(unbound_formset.empty_form)
- empty_forms.append(bound_formset.empty_form)
-
- # Empty forms should be unbound
- self.assertFalse(empty_forms[0].is_bound)
- self.assertFalse(empty_forms[1].is_bound)
-
- # The empty forms should be equal.
- self.assertHTMLEqual(empty_forms[0].as_p(), empty_forms[1].as_p())
-
-class TestEmptyFormSet(TestCase):
- def test_empty_formset_is_valid(self):
- """Test that an empty formset still calls clean()"""
- EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate)
- formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form")
- formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form")
- self.assertFalse(formset.is_valid())
- self.assertFalse(formset2.is_valid())
-
- def test_empty_formset_media(self):
- """Make sure media is available on empty formset, refs #19545"""
- class MediaForm(Form):
- class Media:
- js = ('some-file.js',)
- self.assertIn('some-file.js', str(formset_factory(MediaForm, extra=0)().media))
-
- def test_empty_formset_is_multipart(self):
- """Make sure `is_multipart()` works with empty formset, refs #19545"""
- class FileForm(Form):
- file = FileField()
- self.assertTrue(formset_factory(FileForm, extra=0)().is_multipart())
diff --git a/tests/forms_tests/tests/input_formats.py b/tests/forms_tests/tests/input_formats.py
deleted file mode 100644
index eb0e8dcad8..0000000000
--- a/tests/forms_tests/tests/input_formats.py
+++ /dev/null
@@ -1,866 +0,0 @@
-from datetime import time, date, datetime
-
-from django import forms
-from django.test.utils import override_settings
-from django.utils.translation import activate, deactivate
-from django.test import SimpleTestCase
-
-
-@override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"], USE_L10N=True)
-class LocalizedTimeTests(SimpleTestCase):
- def setUp(self):
- # nl/formats.py has customized TIME_INPUT_FORMATS:
- # ('%H:%M:%S', '%H.%M:%S', '%H.%M', '%H:%M')
- activate('nl')
-
- def tearDown(self):
- deactivate()
-
- def test_timeField(self):
- "TimeFields can parse dates in the default format"
- f = forms.TimeField()
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13:30:05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip
- text = f.widget._format_value(result)
- self.assertEqual(text, '13:30:05')
-
- # Parse a time in a valid, but non-default format, get a parsed result
- result = f.clean('13:30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
- # ISO formats are accepted, even if not specified in formats.py
- result = f.clean('13:30:05.000155')
- self.assertEqual(result, time(13,30,5,155))
-
- def test_localized_timeField(self):
- "Localized TimeFields act as unlocalized widgets"
- f = forms.TimeField(localize=True)
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13:30:05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13:30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
- def test_timeField_with_inputformat(self):
- "TimeFields with manually specified input formats can accept those formats"
- f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"])
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30.05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:05")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
- def test_localized_timeField_with_inputformat(self):
- "Localized TimeFields with manually specified input formats can accept those formats"
- f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"], localize=True)
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30.05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:05")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
-
-@override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"])
-class CustomTimeInputFormatsTests(SimpleTestCase):
- def test_timeField(self):
- "TimeFields can parse dates in the default format"
- f = forms.TimeField()
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('1:30:05 PM')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip
- text = f.widget._format_value(result)
- self.assertEqual(text, '01:30:05 PM')
-
- # Parse a time in a valid, but non-default format, get a parsed result
- result = f.clean('1:30 PM')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM")
-
- def test_localized_timeField(self):
- "Localized TimeFields act as unlocalized widgets"
- f = forms.TimeField(localize=True)
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('1:30:05 PM')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, '01:30:05 PM')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('01:30 PM')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM")
-
- def test_timeField_with_inputformat(self):
- "TimeFields with manually specified input formats can accept those formats"
- f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"])
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30.05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:05 PM")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM")
-
- def test_localized_timeField_with_inputformat(self):
- "Localized TimeFields with manually specified input formats can accept those formats"
- f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"], localize=True)
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30.05')
- self.assertEqual(result, time(13,30,5))
-
- # # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:05 PM")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13.30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM")
-
-
-class SimpleTimeFormatTests(SimpleTestCase):
- def test_timeField(self):
- "TimeFields can parse dates in the default format"
- f = forms.TimeField()
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13:30:05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:05")
-
- # Parse a time in a valid, but non-default format, get a parsed result
- result = f.clean('13:30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
- def test_localized_timeField(self):
- "Localized TimeFields in a non-localized environment act as unlocalized widgets"
- f = forms.TimeField()
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13:30:05')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:05")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('13:30')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
- def test_timeField_with_inputformat(self):
- "TimeFields with manually specified input formats can accept those formats"
- f = forms.TimeField(input_formats=["%I:%M:%S %p", "%I:%M %p"])
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('1:30:05 PM')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:05")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('1:30 PM')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
- def test_localized_timeField_with_inputformat(self):
- "Localized TimeFields with manually specified input formats can accept those formats"
- f = forms.TimeField(input_formats=["%I:%M:%S %p", "%I:%M %p"], localize=True)
- # Parse a time in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('1:30:05 PM')
- self.assertEqual(result, time(13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:05")
-
- # Parse a time in a valid format, get a parsed result
- result = f.clean('1:30 PM')
- self.assertEqual(result, time(13,30,0))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "13:30:00")
-
-
-@override_settings(DATE_INPUT_FORMATS=["%d/%m/%Y", "%d-%m-%Y"], USE_L10N=True)
-class LocalizedDateTests(SimpleTestCase):
- def setUp(self):
- activate('de')
-
- def tearDown(self):
- deactivate()
-
- def test_dateField(self):
- "DateFields can parse dates in the default format"
- f = forms.DateField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
-
- # ISO formats are accepted, even if not specified in formats.py
- self.assertEqual(f.clean('2010-12-21'), date(2010,12,21))
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip
- text = f.widget._format_value(result)
- self.assertEqual(text, '21.12.2010')
-
- # Parse a date in a valid, but non-default format, get a parsed result
- result = f.clean('21.12.10')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- def test_localized_dateField(self):
- "Localized DateFields act as unlocalized widgets"
- f = forms.DateField(localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, '21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.10')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- def test_dateField_with_inputformat(self):
- "DateFields with manually specified input formats can accept those formats"
- f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"])
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
- self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
- self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12.21.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12-21-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- def test_localized_dateField_with_inputformat(self):
- "Localized DateFields with manually specified input formats can accept those formats"
- f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"], localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
- self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
- self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12.21.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12-21-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
-
-@override_settings(DATE_INPUT_FORMATS=["%d.%m.%Y", "%d-%m-%Y"])
-class CustomDateInputFormatsTests(SimpleTestCase):
- def test_dateField(self):
- "DateFields can parse dates in the default format"
- f = forms.DateField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip
- text = f.widget._format_value(result)
- self.assertEqual(text, '21.12.2010')
-
- # Parse a date in a valid, but non-default format, get a parsed result
- result = f.clean('21-12-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- def test_localized_dateField(self):
- "Localized DateFields act as unlocalized widgets"
- f = forms.DateField(localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, '21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21-12-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- def test_dateField_with_inputformat(self):
- "DateFields with manually specified input formats can accept those formats"
- f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"])
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12.21.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12-21-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- def test_localized_dateField_with_inputformat(self):
- "Localized DateFields with manually specified input formats can accept those formats"
- f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"], localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12.21.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12-21-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010")
-
-class SimpleDateFormatTests(SimpleTestCase):
- def test_dateField(self):
- "DateFields can parse dates in the default format"
- f = forms.DateField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('2010-12-21')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- # Parse a date in a valid, but non-default format, get a parsed result
- result = f.clean('12/21/2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- def test_localized_dateField(self):
- "Localized DateFields in a non-localized environment act as unlocalized widgets"
- f = forms.DateField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('2010-12-21')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12/21/2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- def test_dateField_with_inputformat(self):
- "DateFields with manually specified input formats can accept those formats"
- f = forms.DateField(input_formats=["%d.%m.%Y", "%d-%m-%Y"])
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21-12-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- def test_localized_dateField_with_inputformat(self):
- "Localized DateFields with manually specified input formats can accept those formats"
- f = forms.DateField(input_formats=["%d.%m.%Y", "%d-%m-%Y"], localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21-12-2010')
- self.assertEqual(result, date(2010,12,21))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21")
-
-
-@override_settings(DATETIME_INPUT_FORMATS=["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"], USE_L10N=True)
-class LocalizedDateTimeTests(SimpleTestCase):
- def setUp(self):
- activate('de')
-
- def tearDown(self):
- deactivate()
-
- def test_dateTimeField(self):
- "DateTimeFields can parse dates in the default format"
- f = forms.DateTimeField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
-
- # ISO formats are accepted, even if not specified in formats.py
- self.assertEqual(f.clean('2010-12-21 13:30:05'), datetime(2010,12,21,13,30,5))
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip
- text = f.widget._format_value(result)
- self.assertEqual(text, '21.12.2010 13:30:05')
-
- # Parse a date in a valid, but non-default format, get a parsed result
- result = f.clean('21.12.2010 13:30')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010 13:30:00")
-
- def test_localized_dateTimeField(self):
- "Localized DateTimeFields act as unlocalized widgets"
- f = forms.DateTimeField(localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, '21.12.2010 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('21.12.2010 13:30')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010 13:30:00")
-
- def test_dateTimeField_with_inputformat(self):
- "DateTimeFields with manually specified input formats can accept those formats"
- f = forms.DateTimeField(input_formats=["%H.%M.%S %m.%d.%Y", "%H.%M %m-%d-%Y"])
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05 13:30:05')
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('13.30.05 12.21.2010')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010 13:30:05")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('13.30 12-21-2010')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010 13:30:00")
-
- def test_localized_dateTimeField_with_inputformat(self):
- "Localized DateTimeFields with manually specified input formats can accept those formats"
- f = forms.DateTimeField(input_formats=["%H.%M.%S %m.%d.%Y", "%H.%M %m-%d-%Y"], localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
- self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('13.30.05 12.21.2010')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010 13:30:05")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('13.30 12-21-2010')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "21.12.2010 13:30:00")
-
-
-@override_settings(DATETIME_INPUT_FORMATS=["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"])
-class CustomDateTimeInputFormatsTests(SimpleTestCase):
- def test_dateTimeField(self):
- "DateTimeFields can parse dates in the default format"
- f = forms.DateTimeField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30:05 PM 21/12/2010')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip
- text = f.widget._format_value(result)
- self.assertEqual(text, '01:30:05 PM 21/12/2010')
-
- # Parse a date in a valid, but non-default format, get a parsed result
- result = f.clean('1:30 PM 21-12-2010')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM 21/12/2010")
-
- def test_localized_dateTimeField(self):
- "Localized DateTimeFields act as unlocalized widgets"
- f = forms.DateTimeField(localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30:05 PM 21/12/2010')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, '01:30:05 PM 21/12/2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30 PM 21-12-2010')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM 21/12/2010")
-
- def test_dateTimeField_with_inputformat(self):
- "DateTimeFields with manually specified input formats can accept those formats"
- f = forms.DateTimeField(input_formats=["%m.%d.%Y %H:%M:%S", "%m-%d-%Y %H:%M"])
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12.21.2010 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:05 PM 21/12/2010")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12-21-2010 13:30')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM 21/12/2010")
-
- def test_localized_dateTimeField_with_inputformat(self):
- "Localized DateTimeFields with manually specified input formats can accept those formats"
- f = forms.DateTimeField(input_formats=["%m.%d.%Y %H:%M:%S", "%m-%d-%Y %H:%M"], localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12.21.2010 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:05 PM 21/12/2010")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12-21-2010 13:30')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "01:30:00 PM 21/12/2010")
-
-class SimpleDateTimeFormatTests(SimpleTestCase):
- def test_dateTimeField(self):
- "DateTimeFields can parse dates in the default format"
- f = forms.DateTimeField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('2010-12-21 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:05")
-
- # Parse a date in a valid, but non-default format, get a parsed result
- result = f.clean('12/21/2010 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:05")
-
- def test_localized_dateTimeField(self):
- "Localized DateTimeFields in a non-localized environment act as unlocalized widgets"
- f = forms.DateTimeField()
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('2010-12-21 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:05")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('12/21/2010 13:30:05')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:05")
-
- def test_dateTimeField_with_inputformat(self):
- "DateTimeFields with manually specified input formats can accept those formats"
- f = forms.DateTimeField(input_formats=["%I:%M:%S %p %d.%m.%Y", "%I:%M %p %d-%m-%Y"])
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30:05 PM 21.12.2010')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:05")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30 PM 21-12-2010')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:00")
-
- def test_localized_dateTimeField_with_inputformat(self):
- "Localized DateTimeFields with manually specified input formats can accept those formats"
- f = forms.DateTimeField(input_formats=["%I:%M:%S %p %d.%m.%Y", "%I:%M %p %d-%m-%Y"], localize=True)
- # Parse a date in an unaccepted format; get an error
- self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30:05 PM 21.12.2010')
- self.assertEqual(result, datetime(2010,12,21,13,30,5))
-
- # Check that the parsed result does a round trip to the same format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:05")
-
- # Parse a date in a valid format, get a parsed result
- result = f.clean('1:30 PM 21-12-2010')
- self.assertEqual(result, datetime(2010,12,21,13,30))
-
- # Check that the parsed result does a round trip to default format
- text = f.widget._format_value(result)
- self.assertEqual(text, "2010-12-21 13:30:00")
diff --git a/tests/forms_tests/tests/media.py b/tests/forms_tests/tests/media.py
deleted file mode 100644
index c492a1e539..0000000000
--- a/tests/forms_tests/tests/media.py
+++ /dev/null
@@ -1,907 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget
-from django.template import Template, Context
-from django.test import TestCase
-from django.test.utils import override_settings
-
-
-@override_settings(
- STATIC_URL=None,
- MEDIA_URL='http://media.example.com/media/',
-)
-class FormsMediaTestCase(TestCase):
- """Tests for the media handling on widgets and forms"""
-
- 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), """
-
-
-
-""")
-
- 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), """
-
-
-
-""")
-
- # 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), """
-
-
-
-""")
-
- # Media objects can be interrogated by media type
- self.assertEqual(str(w1.media['css']), """
-""")
-
- self.assertEqual(str(w1.media['js']), """
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- # Check that media addition hasn't affected the original objects
- self.assertEqual(str(w1.media), """
-
-
-
-""")
-
- # 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), """
-""")
-
- 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), """
-""")
-
- # 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), """
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-
-""")
-
- # 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-""")
-
- # 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), """
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-
-""")
-
- 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), """
-
-
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- # 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), """
-
-
-
-
-
-""")
-
- # 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), """
-
-
-
-
-
-
-
-""")
-
- # Media works in templates
- self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """
-
-
-
-
-
-
-""")
-
-
-@override_settings(
- STATIC_URL='http://media.example.com/static/',
- MEDIA_URL='http://media.example.com/media/',
-)
-class StaticFormsMediaTestCase(TestCase):
- """Tests for the media handling on widgets and forms"""
-
- 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), """
-
-
-
-""")
-
- 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), """
-
-
-
-""")
-
- # 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), """
-
-
-
-""")
-
- # Media objects can be interrogated by media type
- self.assertEqual(str(w1.media['css']), """
-""")
-
- self.assertEqual(str(w1.media['js']), """
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- # Check that media addition hasn't affected the original objects
- self.assertEqual(str(w1.media), """
-
-
-
-""")
-
- # 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), """
-""")
-
- 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), """
-""")
-
- # 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), """
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-
-""")
-
- # 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-""")
-
- # 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), """
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-
-""")
-
- 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), """
-
-
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- 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), """
-
-
-
-
-
-""")
-
- # 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), """
-
-
-
-
-
-""")
-
- # 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), """
-
-
-
-
-
-
-
-""")
-
- # Media works in templates
- self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """
-
-
-
-
-
-
-""")
diff --git a/tests/forms_tests/tests/models.py b/tests/forms_tests/tests/models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/forms_tests/tests/regressions.py b/tests/forms_tests/tests/regressions.py
deleted file mode 100644
index ba741bc16b..0000000000
--- a/tests/forms_tests/tests/regressions.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import warnings
-
-from django.forms import *
-from django.test import TestCase
-from django.utils.translation import ugettext_lazy, override
-
-from forms_tests.models import Cheese
-
-
-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.assertHTMLEqual(TestForm(auto_id=False).as_p(), '
F1:
\n
F2:
')
-
- 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.assertHTMLEqual(f.as_p(), '
Username:
')
-
- # Translations are done at rendering time, so multi-lingual apps can define forms)
- with override('de'):
- self.assertHTMLEqual(f.as_p(), '
Benutzername:
')
- with override('pl', deactivate=True):
- self.assertHTMLEqual(f.as_p(), '
Nazwa u\u017cytkownika:
')
-
- 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.assertHTMLEqual(f['field_1'].label_tag(), 'field_1')
- self.assertHTMLEqual(f['field_2'].label_tag(), 'field_2')
-
- # Unicode decoding problems...
- GENDERS = (('\xc5', 'En tied\xe4'), ('\xf8', 'Mies'), ('\xdf', 'Nainen'))
-
- class SomeForm(Form):
- somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label='\xc5\xf8\xdf')
-
- f = SomeForm()
- self.assertHTMLEqual(f.as_p(), '
\xc5\xf8\xdf:
\n
En tied\xe4
\n
Mies
\n
Nainen
\n
')
-
- # Testing choice validation with UTF-8 bytestrings as input (these are the
- # Russian abbreviations "мес." and "шт.".
- UNITS = ((b'\xd0\xbc\xd0\xb5\xd1\x81.', b'\xd0\xbc\xd0\xb5\xd1\x81.'),
- (b'\xd1\x88\xd1\x82.', b'\xd1\x88\xd1\x82.'))
- f = ChoiceField(choices=UNITS)
- with warnings.catch_warnings():
- # Ignore UnicodeWarning
- warnings.simplefilter("ignore")
- self.assertEqual(f.clean('\u0448\u0442.'), '\u0448\u0442.')
- self.assertEqual(f.clean(b'\xd1\x88\xd1\x82.'), '\u0448\u0442.')
-
- # Translated error messages used to be buggy.
- with override('ru'):
- f = SomeForm({})
- self.assertHTMLEqual(f.as_p(), '
')
-
- # 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': '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.assertHTMLEqual(f.as_p(), '
(Hidden field data) This field is required.
\n
')
- self.assertHTMLEqual(f.as_table(), '
(Hidden field data) This field is required.
')
-
- 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': '",
+ 'special_safe_name': "Do not escape"
+ }, auto_id=False)
+ self.assertHTMLEqual(f.as_table(), """
<em>Special</em> Field:
Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'
+
Special Field:
'Do not escape' is a safe string
""")
+
+ def test_validating_multiple_fields(self):
+ # There are a couple of ways to do multiple-field validation. If you want the
+ # validation message to be associated with a particular field, implement the
+ # clean_XXX() method on the Form, where XXX is the field name. As in
+ # Field.clean(), the clean_XXX() method should return the cleaned value. In the
+ # clean_XXX() method, you have access to self.cleaned_data, which is a dictionary
+ # of all the data that has been cleaned *so far*, in order by the fields,
+ # including the current field (e.g., the field XXX if you're in clean_XXX()).
+ class UserRegistration(Form):
+ username = CharField(max_length=10)
+ password1 = CharField(widget=PasswordInput)
+ password2 = CharField(widget=PasswordInput)
+
+ def clean_password2(self):
+ if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
+ raise ValidationError('Please make sure your passwords match.')
+
+ return self.cleaned_data['password2']
+
+ f = UserRegistration(auto_id=False)
+ self.assertEqual(f.errors, {})
+ f = UserRegistration({}, auto_id=False)
+ self.assertEqual(f.errors['username'], ['This field is required.'])
+ self.assertEqual(f.errors['password1'], ['This field is required.'])
+ self.assertEqual(f.errors['password2'], ['This field is required.'])
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
+ self.assertEqual(f.errors['password2'], ['Please make sure your passwords match.'])
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
+ self.assertEqual(f.errors, {})
+ self.assertEqual(f.cleaned_data['username'], 'adrian')
+ self.assertEqual(f.cleaned_data['password1'], 'foo')
+ self.assertEqual(f.cleaned_data['password2'], 'foo')
+
+ # Another way of doing multiple-field validation is by implementing the
+ # Form's clean() method. If you do this, any ValidationError raised by that
+ # method will not be associated with a particular field; it will have a
+ # special-case association with the field named '__all__'.
+ # Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of
+ # all the fields/values that have *not* raised a ValidationError. Also note
+ # Form.clean() is required to return a dictionary of all clean data.
+ class UserRegistration(Form):
+ username = CharField(max_length=10)
+ password1 = CharField(widget=PasswordInput)
+ password2 = CharField(widget=PasswordInput)
+
+ def clean(self):
+ if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
+ raise ValidationError('Please make sure your passwords match.')
+
+ return self.cleaned_data
+
+ f = UserRegistration(auto_id=False)
+ self.assertEqual(f.errors, {})
+ f = UserRegistration({}, auto_id=False)
+ self.assertHTMLEqual(f.as_table(), """
Username:
This field is required.
+
Password1:
This field is required.
+
Password2:
This field is required.
""")
+ self.assertEqual(f.errors['username'], ['This field is required.'])
+ self.assertEqual(f.errors['password1'], ['This field is required.'])
+ self.assertEqual(f.errors['password2'], ['This field is required.'])
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
+ self.assertEqual(f.errors['__all__'], ['Please make sure your passwords match.'])
+ self.assertHTMLEqual(f.as_table(), """
Please make sure your passwords match.
+
Username:
+
Password1:
+
Password2:
""")
+ self.assertHTMLEqual(f.as_ul(), """
Please make sure your passwords match.
+
Username:
+
Password1:
+
Password2:
""")
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
+ self.assertEqual(f.errors, {})
+ self.assertEqual(f.cleaned_data['username'], 'adrian')
+ self.assertEqual(f.cleaned_data['password1'], 'foo')
+ self.assertEqual(f.cleaned_data['password2'], 'foo')
+
+ def test_dynamic_construction(self):
+ # It's possible to construct a Form dynamically by adding to the self.fields
+ # dictionary in __init__(). Don't forget to call Form.__init__() within the
+ # subclass' __init__().
+ class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+
+ def __init__(self, *args, **kwargs):
+ super(Person, self).__init__(*args, **kwargs)
+ self.fields['birthday'] = DateField()
+
+ p = Person(auto_id=False)
+ self.assertHTMLEqual(p.as_table(), """
First name:
+
Last name:
+
Birthday:
""")
+
+ # Instances of a dynamic Form do not persist fields from one Form instance to
+ # the next.
+ class MyForm(Form):
+ def __init__(self, data=None, auto_id=False, field_list=[]):
+ Form.__init__(self, data, auto_id=auto_id)
+
+ for field in field_list:
+ self.fields[field[0]] = field[1]
+
+ field_list = [('field1', CharField()), ('field2', CharField())]
+ my_form = MyForm(field_list=field_list)
+ self.assertHTMLEqual(my_form.as_table(), """
""")
+
+ # Similarly, changes to field attributes do not persist from one Form instance
+ # to the next.
+ class Person(Form):
+ first_name = CharField(required=False)
+ last_name = CharField(required=False)
+
+ def __init__(self, names_required=False, *args, **kwargs):
+ super(Person, self).__init__(*args, **kwargs)
+
+ if names_required:
+ self.fields['first_name'].required = True
+ self.fields['first_name'].widget.attrs['class'] = 'required'
+ self.fields['last_name'].required = True
+ self.fields['last_name'].widget.attrs['class'] = 'required'
+
+ f = Person(names_required=False)
+ self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (False, False))
+ self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({}, {}))
+ f = Person(names_required=True)
+ self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (True, True))
+ self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({'class': 'required'}, {'class': 'required'}))
+ f = Person(names_required=False)
+ self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (False, False))
+ self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({}, {}))
+
+ class Person(Form):
+ first_name = CharField(max_length=30)
+ last_name = CharField(max_length=30)
+
+ def __init__(self, name_max_length=None, *args, **kwargs):
+ super(Person, self).__init__(*args, **kwargs)
+
+ if name_max_length:
+ self.fields['first_name'].max_length = name_max_length
+ self.fields['last_name'].max_length = name_max_length
+
+ f = Person(name_max_length=None)
+ self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (30, 30))
+ f = Person(name_max_length=20)
+ self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (20, 20))
+ f = Person(name_max_length=None)
+ self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (30, 30))
+
+ # Similarly, choices do not persist from one Form instance to the next.
+ # Refs #15127.
+ class Person(Form):
+ first_name = CharField(required=False)
+ last_name = CharField(required=False)
+ gender = ChoiceField(choices=(('f', 'Female'), ('m', 'Male')))
+
+ def __init__(self, allow_unspec_gender=False, *args, **kwargs):
+ super(Person, self).__init__(*args, **kwargs)
+
+ if allow_unspec_gender:
+ self.fields['gender'].choices += (('u', 'Unspecified'),)
+
+ f = Person()
+ self.assertEqual(f['gender'].field.choices, [('f', 'Female'), ('m', 'Male')])
+ f = Person(allow_unspec_gender=True)
+ self.assertEqual(f['gender'].field.choices, [('f', 'Female'), ('m', 'Male'), ('u', 'Unspecified')])
+ f = Person()
+ self.assertEqual(f['gender'].field.choices, [('f', 'Female'), ('m', 'Male')])
+
+ def test_validators_independence(self):
+ """ Test that we are able to modify a form field validators list without polluting
+ other forms """
+ from django.core.validators import MaxValueValidator
+ class MyForm(Form):
+ myfield = CharField(max_length=25)
+
+ f1 = MyForm()
+ f2 = MyForm()
+
+ f1.fields['myfield'].validators[0] = MaxValueValidator(12)
+ self.assertFalse(f1.fields['myfield'].validators[0] == f2.fields['myfield'].validators[0])
+
+ def test_hidden_widget(self):
+ # HiddenInput widgets are displayed differently in the as_table(), as_ul())
+ # and as_p() output of a Form -- their verbose names are not displayed, and a
+ # separate row is not displayed. They're displayed in the last row of the
+ # form, directly after that row's form element.
+ class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+ hidden_text = CharField(widget=HiddenInput)
+ birthday = DateField()
+
+ p = Person(auto_id=False)
+ self.assertHTMLEqual(p.as_table(), """
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(p.as_ul(), """
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(p.as_p(), """
First name:
+
Last name:
+
Birthday:
""")
+
+ # With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
+ p = Person(auto_id='id_%s')
+ self.assertHTMLEqual(p.as_table(), """
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(p.as_ul(), """
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(p.as_p(), """
First name:
+
Last name:
+
Birthday:
""")
+
+ # If a field with a HiddenInput has errors, the as_table() and as_ul() output
+ # will include the error message(s) with the text "(Hidden field [fieldname]) "
+ # prepended. This message is displayed at the top of the output, regardless of
+ # its field's order in the form.
+ p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}, auto_id=False)
+ self.assertHTMLEqual(p.as_table(), """
(Hidden field hidden_text) This field is required.
+
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(p.as_ul(), """
(Hidden field hidden_text) This field is required.
+
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(p.as_p(), """
(Hidden field hidden_text) This field is required.
+
First name:
+
Last name:
+
Birthday:
""")
+
+ # A corner case: It's possible for a form to have only HiddenInputs.
+ class TestForm(Form):
+ foo = CharField(widget=HiddenInput)
+ bar = CharField(widget=HiddenInput)
+
+ p = TestForm(auto_id=False)
+ self.assertHTMLEqual(p.as_table(), '')
+ self.assertHTMLEqual(p.as_ul(), '')
+ self.assertHTMLEqual(p.as_p(), '')
+
+ def test_field_order(self):
+ # A Form's fields are displayed in the same order in which they were defined.
+ class TestForm(Form):
+ field1 = CharField()
+ field2 = CharField()
+ field3 = CharField()
+ field4 = CharField()
+ field5 = CharField()
+ field6 = CharField()
+ field7 = CharField()
+ field8 = CharField()
+ field9 = CharField()
+ field10 = CharField()
+ field11 = CharField()
+ field12 = CharField()
+ field13 = CharField()
+ field14 = CharField()
+
+ p = TestForm(auto_id=False)
+ self.assertHTMLEqual(p.as_table(), """
Field1:
+
Field2:
+
Field3:
+
Field4:
+
Field5:
+
Field6:
+
Field7:
+
Field8:
+
Field9:
+
Field10:
+
Field11:
+
Field12:
+
Field13:
+
Field14:
""")
+
+ def test_form_html_attributes(self):
+ # Some Field classes have an effect on the HTML attributes of their associated
+ # Widget. If you set max_length in a CharField and its associated widget is
+ # either a TextInput or PasswordInput, then the widget's rendered HTML will
+ # include the "maxlength" attribute.
+ class UserRegistration(Form):
+ username = CharField(max_length=10) # uses TextInput by default
+ password = CharField(max_length=10, widget=PasswordInput)
+ realname = CharField(max_length=10, widget=TextInput) # redundantly define widget, just to test
+ address = CharField() # no max_length defined here
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
+
Realname:
+
Address:
""")
+
+ # If you specify a custom "attrs" that includes the "maxlength" attribute,
+ # the Field's max_length attribute will override whatever "maxlength" you specify
+ # in "attrs".
+ class UserRegistration(Form):
+ username = CharField(max_length=10, widget=TextInput(attrs={'maxlength': 20}))
+ password = CharField(max_length=10, widget=PasswordInput)
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+
+ def test_specifying_labels(self):
+ # You can specify the label for a field by using the 'label' argument to a Field
+ # class. If you don't specify 'label', Django will use the field name with
+ # underscores converted to spaces, and the initial letter capitalized.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, label='Your username')
+ password1 = CharField(widget=PasswordInput)
+ password2 = CharField(widget=PasswordInput, label='Contraseña (de nuevo)')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Your username:
+
Password1:
+
Contraseña (de nuevo):
""")
+
+ # Labels for as_* methods will only end in a colon if they don't end in other
+ # punctuation already.
+ class Questions(Form):
+ q1 = CharField(label='The first question')
+ q2 = CharField(label='What is your name?')
+ q3 = CharField(label='The answer to life is:')
+ q4 = CharField(label='Answer this question!')
+ q5 = CharField(label='The last question. Period.')
+
+ self.assertHTMLEqual(Questions(auto_id=False).as_p(), """
""")
+
+ # If a label is set to the empty string for a field, that field won't get a label.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, label='')
+ password = CharField(widget=PasswordInput)
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
+
Password:
""")
+ p = UserRegistration(auto_id='id_%s')
+ self.assertHTMLEqual(p.as_ul(), """
+
Password:
""")
+
+ # If label is None, Django will auto-create the label from the field name. This
+ # is default behavior.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, label=None)
+ password = CharField(widget=PasswordInput)
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+ p = UserRegistration(auto_id='id_%s')
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+
+ def test_label_suffix(self):
+ # You can specify the 'label_suffix' argument to a Form class to modify the
+ # punctuation symbol used at the end of a label. By default, the colon (:) is
+ # used, and is only appended to the label if the label doesn't already end with a
+ # punctuation symbol: ., !, ? or :. If you specify a different suffix, it will
+ # be appended regardless of the last character of the label.
+ class FavoriteForm(Form):
+ color = CharField(label='Favorite color?')
+ animal = CharField(label='Favorite animal')
+
+ f = FavoriteForm(auto_id=False)
+ self.assertHTMLEqual(f.as_ul(), """
Favorite color?
+
Favorite animal:
""")
+ f = FavoriteForm(auto_id=False, label_suffix='?')
+ self.assertHTMLEqual(f.as_ul(), """
Favorite color?
+
Favorite animal?
""")
+ f = FavoriteForm(auto_id=False, label_suffix='')
+ self.assertHTMLEqual(f.as_ul(), """
Favorite color?
+
Favorite animal
""")
+ f = FavoriteForm(auto_id=False, label_suffix='\u2192')
+ self.assertHTMLEqual(f.as_ul(), '
Favorite color?
\n
Favorite animal\u2192
')
+
+ def test_initial_data(self):
+ # You can specify initial data for a field by using the 'initial' argument to a
+ # Field class. This initial data is displayed when a Form is rendered with *no*
+ # data. It is not displayed when a Form is rendered with any data (including an
+ # empty dictionary). Also, the initial value is *not* used if data for a
+ # particular required field isn't provided.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, initial='django')
+ password = CharField(widget=PasswordInput)
+
+ # Here, we're not submitting any data, so the initial value will be displayed.)
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+
+ # Here, we're submitting data, so the initial value will *not* be displayed.
+ p = UserRegistration({}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
This field is required.
Username:
+
This field is required.
Password:
""")
+ p = UserRegistration({'username': ''}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
This field is required.
Username:
+
This field is required.
Password:
""")
+ p = UserRegistration({'username': 'foo'}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
This field is required.
Password:
""")
+
+ # An 'initial' value is *not* used as a fallback if data is not provided. In this
+ # example, we don't provide a value for 'username', and the form raises a
+ # validation error rather than using the initial value for 'username'.
+ p = UserRegistration({'password': 'secret'})
+ self.assertEqual(p.errors['username'], ['This field is required.'])
+ self.assertFalse(p.is_valid())
+
+ def test_dynamic_initial_data(self):
+ # The previous technique dealt with "hard-coded" initial data, but it's also
+ # possible to specify initial data after you've already created the Form class
+ # (i.e., at runtime). Use the 'initial' parameter to the Form constructor. This
+ # should be a dictionary containing initial values for one or more fields in the
+ # form, keyed by field name.
+ class UserRegistration(Form):
+ username = CharField(max_length=10)
+ password = CharField(widget=PasswordInput)
+
+ # Here, we're not submitting any data, so the initial value will be displayed.)
+ p = UserRegistration(initial={'username': 'django'}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+ p = UserRegistration(initial={'username': 'stephane'}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+
+ # The 'initial' parameter is meaningless if you pass data.
+ p = UserRegistration({}, initial={'username': 'django'}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
""")
+
+ # A dynamic 'initial' value is *not* used as a fallback if data is not provided.
+ # In this example, we don't provide a value for 'username', and the form raises a
+ # validation error rather than using the initial value for 'username'.
+ p = UserRegistration({'password': 'secret'}, initial={'username': 'django'})
+ self.assertEqual(p.errors['username'], ['This field is required.'])
+ self.assertFalse(p.is_valid())
+
+ # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
+ # then the latter will get precedence.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, initial='django')
+ password = CharField(widget=PasswordInput)
+
+ p = UserRegistration(initial={'username': 'babik'}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
""")
+
+ def test_callable_initial_data(self):
+ # The previous technique dealt with raw values as initial data, but it's also
+ # possible to specify callable data.
+ class UserRegistration(Form):
+ username = CharField(max_length=10)
+ password = CharField(widget=PasswordInput)
+ options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')])
+
+ # We need to define functions that get called later.)
+ def initial_django():
+ return 'django'
+
+ def initial_stephane():
+ return 'stephane'
+
+ def initial_options():
+ return ['f','b']
+
+ def initial_other_options():
+ return ['b','w']
+
+ # Here, we're not submitting any data, so the initial value will be displayed.)
+ p = UserRegistration(initial={'username': initial_django, 'options': initial_options}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username:
+
Password:
+
Options:
""")
+
+ # The 'initial' parameter is meaningless if you pass data.
+ p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
""")
+
+ # A callable 'initial' value is *not* used as a fallback if data is not provided.
+ # In this example, we don't provide a value for 'username', and the form raises a
+ # validation error rather than using the initial value for 'username'.
+ p = UserRegistration({'password': 'secret'}, initial={'username': initial_django, 'options': initial_options})
+ self.assertEqual(p.errors['username'], ['This field is required.'])
+ self.assertFalse(p.is_valid())
+
+ # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
+ # then the latter will get precedence.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, initial=initial_django)
+ password = CharField(widget=PasswordInput)
+ options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')], initial=initial_other_options)
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
""")
+
+ def test_changed_data(self):
+ class Person(Form):
+ first_name = CharField(initial='Hans')
+ last_name = CharField(initial='Greatel')
+ birthday = DateField(initial=datetime.date(1974, 8, 16))
+
+ p = Person(data={'first_name': 'Hans', 'last_name': 'Scrmbl',
+ 'birthday': '1974-08-16'})
+ self.assertTrue(p.is_valid())
+ self.assertNotIn('first_name', p.changed_data)
+ self.assertIn('last_name', p.changed_data)
+ self.assertNotIn('birthday', p.changed_data)
+
+ # Test that field raising ValidationError is always in changed_data
+ class PedanticField(forms.Field):
+ def to_python(self, value):
+ raise ValidationError('Whatever')
+
+ class Person2(Person):
+ pedantic = PedanticField(initial='whatever', show_hidden_initial=True)
+
+ p = Person2(data={'first_name': 'Hans', 'last_name': 'Scrmbl',
+ 'birthday': '1974-08-16', 'initial-pedantic': 'whatever'})
+ self.assertFalse(p.is_valid())
+ self.assertIn('pedantic', p.changed_data)
+
+ def test_boundfield_values(self):
+ # It's possible to get to the value which would be used for rendering
+ # the widget for a field by using the BoundField's value method.
+
+ class UserRegistration(Form):
+ username = CharField(max_length=10, initial='djangonaut')
+ password = CharField(widget=PasswordInput)
+
+ unbound = UserRegistration()
+ bound = UserRegistration({'password': 'foo'})
+ self.assertEqual(bound['username'].value(), None)
+ self.assertEqual(unbound['username'].value(), 'djangonaut')
+ self.assertEqual(bound['password'].value(), 'foo')
+ self.assertEqual(unbound['password'].value(), None)
+
+ def test_help_text(self):
+ # You can specify descriptive text for a field by using the 'help_text' argument)
+ class UserRegistration(Form):
+ username = CharField(max_length=10, help_text='e.g., user@example.com')
+ password = CharField(widget=PasswordInput, help_text='Wählen Sie mit Bedacht.')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
+
Password: Wählen Sie mit Bedacht.
""")
+ self.assertHTMLEqual(p.as_p(), """
Username: e.g., user@example.com
+
Password: Wählen Sie mit Bedacht.
""")
+ self.assertHTMLEqual(p.as_table(), """
Username:
e.g., user@example.com
+
Password:
Wählen Sie mit Bedacht.
""")
+
+ # The help text is displayed whether or not data is provided for the form.
+ p = UserRegistration({'username': 'foo'}, auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
+
This field is required.
Password: Wählen Sie mit Bedacht.
""")
+
+ # help_text is not displayed for hidden fields. It can be used for documentation
+ # purposes, though.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, help_text='e.g., user@example.com')
+ password = CharField(widget=PasswordInput)
+ next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
+
Password:
""")
+
+ def test_subclassing_forms(self):
+ # You can subclass a Form to add fields. The resulting form subclass will have
+ # all of the fields of the parent Form, plus whichever fields you define in the
+ # subclass.
+ class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birthday = DateField()
+
+ class Musician(Person):
+ instrument = CharField()
+
+ p = Person(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
First name:
+
Last name:
+
Birthday:
""")
+ m = Musician(auto_id=False)
+ self.assertHTMLEqual(m.as_ul(), """
First name:
+
Last name:
+
Birthday:
+
Instrument:
""")
+
+ # Yes, you can subclass multiple forms. The fields are added in the order in
+ # which the parent classes are listed.
+ class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birthday = DateField()
+
+ class Instrument(Form):
+ instrument = CharField()
+
+ class Beatle(Person, Instrument):
+ haircut_type = CharField()
+
+ b = Beatle(auto_id=False)
+ self.assertHTMLEqual(b.as_ul(), """
First name:
+
Last name:
+
Birthday:
+
Instrument:
+
Haircut type:
""")
+
+ def test_forms_with_prefixes(self):
+ # Sometimes it's necessary to have multiple forms display on the same HTML page,
+ # or multiple copies of the same form. We can accomplish this with form prefixes.
+ # Pass the keyword argument 'prefix' to the Form constructor to use this feature.
+ # This value will be prepended to each HTML form field name. One way to think
+ # about this is "namespaces for HTML forms". Notice that in the data argument,
+ # each field's key has the prefix, in this case 'person1', prepended to the
+ # actual field name.
+ class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birthday = DateField()
+
+ data = {
+ 'person1-first_name': 'John',
+ 'person1-last_name': 'Lennon',
+ 'person1-birthday': '1940-10-9'
+ }
+ p = Person(data, prefix='person1')
+ self.assertHTMLEqual(p.as_ul(), """
First name:
+
Last name:
+
Birthday:
""")
+ self.assertHTMLEqual(str(p['first_name']), '')
+ self.assertHTMLEqual(str(p['last_name']), '')
+ self.assertHTMLEqual(str(p['birthday']), '')
+ self.assertEqual(p.errors, {})
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.cleaned_data['first_name'], 'John')
+ self.assertEqual(p.cleaned_data['last_name'], 'Lennon')
+ self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9))
+
+ # Let's try submitting some bad data to make sure form.errors and field.errors
+ # work as expected.
+ data = {
+ 'person1-first_name': '',
+ 'person1-last_name': '',
+ 'person1-birthday': ''
+ }
+ p = Person(data, prefix='person1')
+ self.assertEqual(p.errors['first_name'], ['This field is required.'])
+ self.assertEqual(p.errors['last_name'], ['This field is required.'])
+ self.assertEqual(p.errors['birthday'], ['This field is required.'])
+ self.assertEqual(p['first_name'].errors, ['This field is required.'])
+ try:
+ p['person1-first_name'].errors
+ self.fail('Attempts to access non-existent fields should fail.')
+ except KeyError:
+ pass
+
+ # In this example, the data doesn't have a prefix, but the form requires it, so
+ # the form doesn't "see" the fields.
+ data = {
+ 'first_name': 'John',
+ 'last_name': 'Lennon',
+ 'birthday': '1940-10-9'
+ }
+ p = Person(data, prefix='person1')
+ self.assertEqual(p.errors['first_name'], ['This field is required.'])
+ self.assertEqual(p.errors['last_name'], ['This field is required.'])
+ self.assertEqual(p.errors['birthday'], ['This field is required.'])
+
+ # With prefixes, a single data dictionary can hold data for multiple instances
+ # of the same form.
+ data = {
+ 'person1-first_name': 'John',
+ 'person1-last_name': 'Lennon',
+ 'person1-birthday': '1940-10-9',
+ 'person2-first_name': 'Jim',
+ 'person2-last_name': 'Morrison',
+ 'person2-birthday': '1943-12-8'
+ }
+ p1 = Person(data, prefix='person1')
+ self.assertTrue(p1.is_valid())
+ self.assertEqual(p1.cleaned_data['first_name'], 'John')
+ self.assertEqual(p1.cleaned_data['last_name'], 'Lennon')
+ self.assertEqual(p1.cleaned_data['birthday'], datetime.date(1940, 10, 9))
+ p2 = Person(data, prefix='person2')
+ self.assertTrue(p2.is_valid())
+ self.assertEqual(p2.cleaned_data['first_name'], 'Jim')
+ self.assertEqual(p2.cleaned_data['last_name'], 'Morrison')
+ self.assertEqual(p2.cleaned_data['birthday'], datetime.date(1943, 12, 8))
+
+ # By default, forms append a hyphen between the prefix and the field name, but a
+ # form can alter that behavior by implementing the add_prefix() method. This
+ # method takes a field name and returns the prefixed field, according to
+ # self.prefix.
+ class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birthday = DateField()
+
+ def add_prefix(self, field_name):
+ return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name
+
+ p = Person(prefix='foo')
+ self.assertHTMLEqual(p.as_ul(), """
First name:
+
Last name:
+
Birthday:
""")
+ data = {
+ 'foo-prefix-first_name': 'John',
+ 'foo-prefix-last_name': 'Lennon',
+ 'foo-prefix-birthday': '1940-10-9'
+ }
+ p = Person(data, prefix='foo')
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.cleaned_data['first_name'], 'John')
+ self.assertEqual(p.cleaned_data['last_name'], 'Lennon')
+ self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9))
+
+ def test_forms_with_null_boolean(self):
+ # NullBooleanField is a bit of a special case because its presentation (widget)
+ # is different than its data. This is handled transparently, though.
+ class Person(Form):
+ name = CharField()
+ is_cool = NullBooleanField()
+
+ p = Person({'name': 'Joe'}, auto_id=False)
+ self.assertHTMLEqual(str(p['is_cool']), """""")
+ p = Person({'name': 'Joe', 'is_cool': '1'}, auto_id=False)
+ self.assertHTMLEqual(str(p['is_cool']), """""")
+ p = Person({'name': 'Joe', 'is_cool': '2'}, auto_id=False)
+ self.assertHTMLEqual(str(p['is_cool']), """""")
+ p = Person({'name': 'Joe', 'is_cool': '3'}, auto_id=False)
+ self.assertHTMLEqual(str(p['is_cool']), """""")
+ p = Person({'name': 'Joe', 'is_cool': True}, auto_id=False)
+ self.assertHTMLEqual(str(p['is_cool']), """""")
+ p = Person({'name': 'Joe', 'is_cool': False}, auto_id=False)
+ self.assertHTMLEqual(str(p['is_cool']), """""")
+
+ def test_forms_with_file_fields(self):
+ # FileFields are a special case because they take their data from the request.FILES,
+ # not request.POST.
+ class FileForm(Form):
+ file1 = FileField()
+
+ f = FileForm(auto_id=False)
+ self.assertHTMLEqual(f.as_table(), '
')
+ self.assertTrue(f.is_valid())
+
+ f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False)
+ self.assertHTMLEqual(f.as_table(), '
File1:
')
+
+ def test_basic_processing_in_view(self):
+ class UserRegistration(Form):
+ username = CharField(max_length=10)
+ password1 = CharField(widget=PasswordInput)
+ password2 = CharField(widget=PasswordInput)
+
+ def clean(self):
+ if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
+ raise ValidationError('Please make sure your passwords match.')
+
+ return self.cleaned_data
+
+ def my_function(method, post_data):
+ if method == 'POST':
+ form = UserRegistration(post_data, auto_id=False)
+ else:
+ form = UserRegistration(auto_id=False)
+
+ if form.is_valid():
+ return 'VALID: %r' % sorted(six.iteritems(form.cleaned_data))
+
+ t = Template('')
+ return t.render(Context({'form': form}))
+
+ # Case 1: GET (an empty form, with no errors).)
+ self.assertHTMLEqual(my_function('GET', {}), """""")
+ # Case 2: POST with erroneous data (a redisplayed form, with errors).)
+ self.assertHTMLEqual(my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'}), """""")
+ # Case 3: POST with valid data (the success message).)
+ self.assertEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}),
+ str_prefix("VALID: [('password1', %(_)s'secret'), ('password2', %(_)s'secret'), ('username', %(_)s'adrian')]"))
+
+ def test_templates_with_forms(self):
+ class UserRegistration(Form):
+ username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.")
+ password1 = CharField(widget=PasswordInput)
+ password2 = CharField(widget=PasswordInput)
+
+ def clean(self):
+ if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
+ raise ValidationError('Please make sure your passwords match.')
+
+ return self.cleaned_data
+
+ # You have full flexibility in displaying form fields in a template. Just pass a
+ # Form instance to the template, and use "dot" access to refer to individual
+ # fields. Note, however, that this flexibility comes with the responsibility of
+ # displaying all the errors, including any that might not be associated with a
+ # particular field.
+ t = Template('''''')
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)})), """""")
+
+ # Use form.[field].label to output a field's label. You can specify the label for
+ # a field by using the 'label' argument to a Field class. If you don't specify
+ # 'label', Django will use the field name with underscores converted to spaces,
+ # and the initial letter capitalized.
+ t = Template('''''')
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
+
+ # User form.[field].label_tag to output a field's label with a tag
+ # wrapped around it, but *only* if the given field has an "id" attribute.
+ # Recall from above that passing the "auto_id" argument to a Form gives each
+ # field an "id" attribute.
+ t = Template('''''')
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id='id_%s')})), """""")
+
+ # User form.[field].help_text to output a field's help text. If the given field
+ # does not have help text, nothing will be output.
+ t = Template('''''')
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """""")
+ self.assertEqual(Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})), '')
+
+ # To display the errors that aren't associated with a particular field -- e.g.,
+ # the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the
+ # template. If used on its own, it is displayed as a
(or an empty string, if
+ # the list of errors is empty). You can also use it in {% if %} statements.
+ t = Template('''
''')
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})), """""")
+ t = Template('''''')
+ self.assertHTMLEqual(t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})), """""")
+
+ def test_empty_permitted(self):
+ # Sometimes (pretty much in formsets) we want to allow a form to pass validation
+ # if it is completely empty. We can accomplish this by using the empty_permitted
+ # agrument to a form constructor.
+ class SongForm(Form):
+ artist = CharField()
+ name = CharField()
+
+ # First let's show what happens id empty_permitted=False (the default):
+ data = {'artist': '', 'song': ''}
+ form = SongForm(data, empty_permitted=False)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors, {'name': ['This field is required.'], 'artist': ['This field is required.']})
+ self.assertEqual(form.cleaned_data, {})
+
+ # Now let's show what happens when empty_permitted=True and the form is empty.
+ form = SongForm(data, empty_permitted=True)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.errors, {})
+ self.assertEqual(form.cleaned_data, {})
+
+ # But if we fill in data for one of the fields, the form is no longer empty and
+ # the whole thing must pass validation.
+ data = {'artist': 'The Doors', 'song': ''}
+ form = SongForm(data, empty_permitted=False)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors, {'name': ['This field is required.']})
+ self.assertEqual(form.cleaned_data, {'artist': 'The Doors'})
+
+ # If a field is not given in the data then None is returned for its data. Lets
+ # make sure that when checking for empty_permitted that None is treated
+ # accordingly.
+ data = {'artist': None, 'song': ''}
+ form = SongForm(data, empty_permitted=True)
+ self.assertTrue(form.is_valid())
+
+ # However, we *really* need to be sure we are checking for None as any data in
+ # initial that returns False on a boolean call needs to be treated literally.
+ class PriceForm(Form):
+ amount = FloatField()
+ qty = IntegerField()
+
+ data = {'amount': '0.0', 'qty': ''}
+ form = PriceForm(data, initial={'amount': 0.0}, empty_permitted=True)
+ self.assertTrue(form.is_valid())
+
+ def test_extracting_hidden_and_visible(self):
+ class SongForm(Form):
+ token = CharField(widget=HiddenInput)
+ artist = CharField()
+ name = CharField()
+
+ form = SongForm()
+ self.assertEqual([f.name for f in form.hidden_fields()], ['token'])
+ self.assertEqual([f.name for f in form.visible_fields()], ['artist', 'name'])
+
+ def test_hidden_initial_gets_id(self):
+ class MyForm(Form):
+ field1 = CharField(max_length=50, show_hidden_initial=True)
+
+ self.assertHTMLEqual(MyForm().as_table(), '
""")
+
+ def test_label_split_datetime_not_displayed(self):
+ class EventForm(Form):
+ happened_at = SplitDateTimeField(widget=widgets.SplitHiddenDateTimeWidget)
+
+ form = EventForm()
+ self.assertHTMLEqual(form.as_ul(), '')
+
+ def test_multivalue_field_validation(self):
+ def bad_names(value):
+ if value == 'bad value':
+ raise ValidationError('bad value not allowed')
+
+ class NameField(MultiValueField):
+ def __init__(self, fields=(), *args, **kwargs):
+ fields = (CharField(label='First name', max_length=10),
+ CharField(label='Last name', max_length=10))
+ super(NameField, self).__init__(fields=fields, *args, **kwargs)
+
+ def compress(self, data_list):
+ return ' '.join(data_list)
+
+ class NameForm(Form):
+ name = NameField(validators=[bad_names])
+
+ form = NameForm(data={'name' : ['bad', 'value']})
+ form.full_clean()
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors, {'name': ['bad value not allowed']})
+ form = NameForm(data={'name' : ['should be overly', 'long for the field names']})
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors, {'name': ['Ensure this value has at most 10 characters (it has 16).',
+ 'Ensure this value has at most 10 characters (it has 24).']})
+ form = NameForm(data={'name' : ['fname', 'lname']})
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data, {'name' : 'fname lname'})
+
+ def test_custom_empty_values(self):
+ """
+ Test that form fields can customize what is considered as an empty value
+ for themselves (#19997).
+ """
+ class CustomJSONField(CharField):
+ empty_values = [None, '']
+ def to_python(self, value):
+ # Fake json.loads
+ if value == '{}':
+ return {}
+ return super(CustomJSONField, self).to_python(value)
+
+ class JSONForm(forms.Form):
+ json = CustomJSONField()
+
+ form = JSONForm(data={'json': '{}'});
+ form.full_clean()
+ self.assertEqual(form.cleaned_data, {'json' : {}})
+
+ def test_boundfield_label_tag(self):
+ class SomeForm(Form):
+ field = CharField()
+ boundfield = SomeForm()['field']
+
+ testcases = [ # (args, kwargs, expected)
+ # without anything: just print the
+ ((), {}, 'Field'),
+
+ # passing just one argument: overrides the field's label
+ (('custom',), {}, 'custom'),
+
+ # the overriden label is escaped
+ (('custom&',), {}, 'custom&'),
+ ((mark_safe('custom&'),), {}, 'custom&'),
+
+ # Passing attrs to add extra attributes on the
+ ((), {'attrs': {'class': 'pretty'}}, 'Field')
+ ]
+
+ for args, kwargs, expected in testcases:
+ self.assertHTMLEqual(boundfield.label_tag(*args, **kwargs), expected)
+
+ def test_boundfield_label_tag_no_id(self):
+ """
+ If a widget has no id, label_tag just returns the text with no
+ surrounding .
+ """
+ class SomeForm(Form):
+ field = CharField()
+ boundfield = SomeForm(auto_id='')['field']
+
+ self.assertHTMLEqual(boundfield.label_tag(), 'Field')
+ self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&')
diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py
new file mode 100644
index 0000000000..4ac3c5ecf1
--- /dev/null
+++ b/tests/forms_tests/tests/test_formsets.py
@@ -0,0 +1,1091 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.forms import (CharField, DateField, FileField, Form, IntegerField,
+ ValidationError, formsets)
+from django.forms.formsets import BaseFormSet, formset_factory
+from django.forms.util import ErrorList
+from django.test 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'])
+
+
+class EmptyFsetWontValidate(BaseFormSet):
+ def clean(self):
+ raise ValidationError("Clean method called")
+
+
+# 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.assertHTMLEqual(str(formset), """
+
Choice:
+
Votes:
""")
+
+ # 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': 'Calexico'}])
+
+ # If a FormSet was not passed any data, its is_valid and has_changed
+ # methods should return False.
+ formset = ChoiceFormSet()
+ self.assertFalse(formset.is_valid())
+ self.assertFalse(formset.has_changed())
+
+ 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': ['This field is required.']}])
+
+ def test_formset_has_changed(self):
+ # FormSet instances has_changed method will be True if any data is
+ # passed to his forms, even if the formset didn't validate
+ 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': '',
+ 'choices-0-votes': '',
+ }
+ blank_formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(blank_formset.has_changed())
+
+ # invalid formset test
+ data['choices-0-choice'] = 'Calexico'
+ invalid_formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(invalid_formset.is_valid())
+ self.assertTrue(invalid_formset.has_changed())
+
+ # valid formset test
+ data['choices-0-votes'] = '100'
+ valid_formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(valid_formset.is_valid())
+ self.assertTrue(valid_formset.has_changed())
+
+ 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': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
+
Votes:
+
Choice:
+
Votes:
""")
+
+ # 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': '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': ['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': ['This field is required.'], 'choice': ['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.assertHTMLEqual('\n'.join(form_output), """
Choice:
+
Votes:
+
Choice:
+
Votes:
+
Choice:
+
Votes:
""")
+
+ # 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': 'Calexico'}, {}, {}])
+
+ def test_formset_validate_max_flag(self):
+ # If validate_max is set and max_num is less than TOTAL_FORMS in the
+ # data, then throw an exception. MAX_NUM_FORMS in the data is
+ # irrelevant here (it's output as a hint for the client but its
+ # value in the returned data is not checked)
+
+ data = {
+ 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
+ 'choices-0-choice': 'Zero',
+ 'choices-0-votes': '0',
+ 'choices-1-choice': 'One',
+ 'choices-1-votes': '1',
+ }
+
+ ChoiceFormSet = formset_factory(Choice, extra=1, max_num=1, validate_max=True)
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(formset.is_valid())
+ self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.'])
+
+ 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': ['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': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
+
Votes:
+
Choice:
+
Votes:
+
Choice:
+
Votes:
+
Choice:
+
Votes:
""")
+
+ # Make sure retrieving an empty form works, and it shows up in the form list
+
+ self.assertTrue(formset.empty_form.empty_permitted)
+ self.assertHTMLEqual(formset.empty_form.as_ul(), """
Choice:
+
Votes:
""")
+
+ 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': 'Calexico', 'votes': 100}, {'choice': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
+
Votes:
+
Delete:
+
Choice:
+
Votes:
+
Delete:
+
Choice:
+
Votes:
+
Delete:
""")
+
+ # 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': 'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': 'Fergie'}, {}])
+ self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'choice': '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': '', 'form-0-DELETE': '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': 'Calexico', 'votes': 100}, {'choice': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
+
Votes:
+
Order:
+
Choice:
+
Votes:
+
Order:
+
Choice:
+
Votes:
+
Order:
""")
+
+ 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': 'The Decemberists'},
+ {'votes': 100, 'ORDER': 1, 'choice': 'Calexico'},
+ {'votes': 900, 'ORDER': 2, 'choice': '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': 'Calexico'},
+ {'votes': 900, 'ORDER': 2, 'choice': 'Fergie'},
+ {'votes': 500, 'ORDER': None, 'choice': 'The Decemberists'},
+ {'votes': 50, 'ORDER': None, 'choice': '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': 'Calexico', 'votes': 100},
+ {'choice': 'Fergie', 'votes': 900},
+ {'choice': '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.assertHTMLEqual('\n'.join(form_output), """
Choice:
+
Votes:
+
Order:
+
Delete:
+
Choice:
+
Votes:
+
Order:
+
Delete:
+
Choice:
+
Votes:
+
Order:
+
Delete:
+
Choice:
+
Votes:
+
Order:
+
Delete:
""")
+
+ # 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': 'The Decemberists'},
+ {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': 'Calexico'},
+ ])
+ self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': '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': '',
+ 'form-0-DELETE': '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 a high default value, leaving the
+ # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
+
Name:
+
Name:
""")
+
+ # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
+
Name:
""")
+
+ # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
""")
+
+ def test_max_num_with_initial_data(self):
+ # max_num with initial data
+
+ # When not passed, max_num will take a high default value, leaving the
+ # number of forms only controlled by the value 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.assertHTMLEqual('\n'.join(form_output), """
Name:
+
Name:
""")
+
+ def test_max_num_zero(self):
+ # If max_num is 0 then no form is rendered at all, regardless of extra,
+ # unless initial data is present. (This changed in the patch for bug
+ # 20084 -- previously max_num=0 trumped initial data)
+
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
+ formset = LimitedFavoriteDrinkFormSet()
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), "")
+
+ # test that initial trumps max_num
+
+ 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), """
Name:
+
Name:
""")
+
+ def test_more_initial_than_max_num(self):
+ # More initial forms than max_num now results in all initial forms
+ # being displayed (but no extra forms). This behavior was changed
+ # from max_num taking precedence in the patch for #20084
+
+ 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.assertHTMLEqual('\n'.join(form_output), """
Name:
+
Name:
+
Name:
""")
+
+ # 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.assertHTMLEqual('\n'.join(form_output), """
Name:
+
Name:
""")
+
+ 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')
+
+ data = {
+ 'form-TOTAL_FORMS': '2',
+ 'form-INITIAL_FORMS': '0',
+ 'form-MAX_NUM_FORMS': '0',
+ }
+ formset = FavoriteDrinksFormSet(data=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(), ['You may only specify a drink once.'])
+
+ def test_formset_iteration(self):
+ # Regression tests for #16455 -- formset instances are iterable
+ ChoiceFormset = formset_factory(Choice, extra=3)
+ formset = ChoiceFormset()
+
+ # confirm iterated formset yields formset.forms
+ forms = list(formset)
+ self.assertEqual(forms, formset.forms)
+ self.assertEqual(len(formset), len(forms))
+
+ # confirm indexing of formset
+ self.assertEqual(formset[0], forms[0])
+ try:
+ formset[3]
+ self.fail('Requesting an invalid formset index should raise an exception')
+ except IndexError:
+ pass
+
+ # Formets can override the default iteration order
+ class BaseReverseFormSet(BaseFormSet):
+ def __iter__(self):
+ return reversed(self.forms)
+
+ def __getitem__(self, idx):
+ return super(BaseReverseFormSet, self).__getitem__(len(self) - idx - 1)
+
+ ReverseChoiceFormset = formset_factory(Choice, BaseReverseFormSet, extra=3)
+ reverse_formset = ReverseChoiceFormset()
+
+ # confirm that __iter__ modifies rendering order
+ # compare forms from "reverse" formset with forms from original formset
+ self.assertEqual(str(reverse_formset[0]), str(forms[-1]))
+ self.assertEqual(str(reverse_formset[1]), str(forms[-2]))
+ self.assertEqual(len(reverse_formset), len(forms))
+
+ def test_formset_nonzero(self):
+ """
+ Formsets with no forms should still evaluate as true.
+ Regression test for #15722
+ """
+ ChoiceFormset = formset_factory(Choice, extra=0)
+ formset = ChoiceFormset()
+ self.assertEqual(len(formset.forms), 0)
+ self.assertTrue(formset)
+
+
+ def test_formset_error_class(self):
+ # Regression tests for #16479 -- formsets form use ErrorList instead of supplied error_class
+ class CustomErrorList(ErrorList):
+ pass
+
+ formset = FavoriteDrinksFormSet(error_class=CustomErrorList)
+ self.assertEqual(formset.forms[0].error_class, CustomErrorList)
+
+ def test_formset_calls_forms_is_valid(self):
+ # Regression tests for #18574 -- make sure formsets call
+ # is_valid() on each form.
+
+ class AnotherChoice(Choice):
+ def is_valid(self):
+ self.is_valid_called = True
+ return super(AnotherChoice, self).is_valid()
+
+ AnotherChoiceFormSet = formset_factory(AnotherChoice)
+ data = {
+ 'choices-TOTAL_FORMS': '1', # number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ }
+ formset = AnotherChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertTrue(all([form.is_valid_called for form in formset.forms]))
+
+ def test_hard_limit_on_instantiated_forms(self):
+ """A formset has a hard limit on the number of forms instantiated."""
+ # reduce the default limit of 1000 temporarily for testing
+ _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM
+ try:
+ formsets.DEFAULT_MAX_NUM = 2
+ ChoiceFormSet = formset_factory(Choice, max_num=1)
+ # someone fiddles with the mgmt form data...
+ formset = ChoiceFormSet(
+ {
+ 'choices-TOTAL_FORMS': '4',
+ 'choices-INITIAL_FORMS': '0',
+ 'choices-MAX_NUM_FORMS': '4',
+ 'choices-0-choice': 'Zero',
+ 'choices-0-votes': '0',
+ 'choices-1-choice': 'One',
+ 'choices-1-votes': '1',
+ 'choices-2-choice': 'Two',
+ 'choices-2-votes': '2',
+ 'choices-3-choice': 'Three',
+ 'choices-3-votes': '3',
+ },
+ prefix='choices',
+ )
+ # But we still only instantiate 3 forms
+ self.assertEqual(len(formset.forms), 3)
+ # and the formset isn't valid
+ self.assertFalse(formset.is_valid())
+ finally:
+ formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM
+
+ def test_increase_hard_limit(self):
+ """Can increase the built-in forms limit via a higher max_num."""
+ # reduce the default limit of 1000 temporarily for testing
+ _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM
+ try:
+ formsets.DEFAULT_MAX_NUM = 3
+ # for this form, we want a limit of 4
+ ChoiceFormSet = formset_factory(Choice, max_num=4)
+ formset = ChoiceFormSet(
+ {
+ 'choices-TOTAL_FORMS': '4',
+ 'choices-INITIAL_FORMS': '0',
+ 'choices-MAX_NUM_FORMS': '4',
+ 'choices-0-choice': 'Zero',
+ 'choices-0-votes': '0',
+ 'choices-1-choice': 'One',
+ 'choices-1-votes': '1',
+ 'choices-2-choice': 'Two',
+ 'choices-2-votes': '2',
+ 'choices-3-choice': 'Three',
+ 'choices-3-votes': '3',
+ },
+ prefix='choices',
+ )
+ # Four forms are instantiated and no exception is raised
+ self.assertEqual(len(formset.forms), 4)
+ finally:
+ formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM
+
+
+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.assertHTMLEqual(formset.as_table(),"""
+
""")
+
+
+# Regression test for #11418 #################################################
+class ArticleForm(Form):
+ title = CharField()
+ pub_date = DateField()
+
+ArticleFormSet = formset_factory(ArticleForm)
+
+class TestIsBoundBehavior(TestCase):
+ def test_no_data_raises_validation_error(self):
+ self.assertRaises(ValidationError, ArticleFormSet, {})
+
+ def test_with_management_data_attrs_work_fine(self):
+ data = {
+ 'form-TOTAL_FORMS': '1',
+ 'form-INITIAL_FORMS': '0',
+ }
+ formset = ArticleFormSet(data)
+ self.assertEqual(0, formset.initial_form_count())
+ self.assertEqual(1, formset.total_form_count())
+ self.assertTrue(formset.is_bound)
+ self.assertTrue(formset.forms[0].is_bound)
+ self.assertTrue(formset.is_valid())
+ self.assertTrue(formset.forms[0].is_valid())
+ self.assertEqual([{}], formset.cleaned_data)
+
+
+ def test_form_errors_are_cought_by_formset(self):
+ data = {
+ 'form-TOTAL_FORMS': '2',
+ 'form-INITIAL_FORMS': '0',
+ 'form-0-title': 'Test',
+ 'form-0-pub_date': '1904-06-16',
+ 'form-1-title': 'Test',
+ 'form-1-pub_date': '', # <-- this date is missing but required
+ }
+ formset = ArticleFormSet(data)
+ self.assertFalse(formset.is_valid())
+ self.assertEqual([{}, {'pub_date': ['This field is required.']}], formset.errors)
+
+ def test_empty_forms_are_unbound(self):
+ data = {
+ 'form-TOTAL_FORMS': '1',
+ 'form-INITIAL_FORMS': '0',
+ 'form-0-title': 'Test',
+ 'form-0-pub_date': '1904-06-16',
+ }
+ unbound_formset = ArticleFormSet()
+ bound_formset = ArticleFormSet(data)
+
+ empty_forms = []
+
+ empty_forms.append(unbound_formset.empty_form)
+ empty_forms.append(bound_formset.empty_form)
+
+ # Empty forms should be unbound
+ self.assertFalse(empty_forms[0].is_bound)
+ self.assertFalse(empty_forms[1].is_bound)
+
+ # The empty forms should be equal.
+ self.assertHTMLEqual(empty_forms[0].as_p(), empty_forms[1].as_p())
+
+class TestEmptyFormSet(TestCase):
+ def test_empty_formset_is_valid(self):
+ """Test that an empty formset still calls clean()"""
+ EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate)
+ formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form")
+ formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form")
+ self.assertFalse(formset.is_valid())
+ self.assertFalse(formset2.is_valid())
+
+ def test_empty_formset_media(self):
+ """Make sure media is available on empty formset, refs #19545"""
+ class MediaForm(Form):
+ class Media:
+ js = ('some-file.js',)
+ self.assertIn('some-file.js', str(formset_factory(MediaForm, extra=0)().media))
+
+ def test_empty_formset_is_multipart(self):
+ """Make sure `is_multipart()` works with empty formset, refs #19545"""
+ class FileForm(Form):
+ file = FileField()
+ self.assertTrue(formset_factory(FileForm, extra=0)().is_multipart())
diff --git a/tests/forms_tests/tests/test_input_formats.py b/tests/forms_tests/tests/test_input_formats.py
new file mode 100644
index 0000000000..eb0e8dcad8
--- /dev/null
+++ b/tests/forms_tests/tests/test_input_formats.py
@@ -0,0 +1,866 @@
+from datetime import time, date, datetime
+
+from django import forms
+from django.test.utils import override_settings
+from django.utils.translation import activate, deactivate
+from django.test import SimpleTestCase
+
+
+@override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"], USE_L10N=True)
+class LocalizedTimeTests(SimpleTestCase):
+ def setUp(self):
+ # nl/formats.py has customized TIME_INPUT_FORMATS:
+ # ('%H:%M:%S', '%H.%M:%S', '%H.%M', '%H:%M')
+ activate('nl')
+
+ def tearDown(self):
+ deactivate()
+
+ def test_timeField(self):
+ "TimeFields can parse dates in the default format"
+ f = forms.TimeField()
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13:30:05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '13:30:05')
+
+ # Parse a time in a valid, but non-default format, get a parsed result
+ result = f.clean('13:30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+ # ISO formats are accepted, even if not specified in formats.py
+ result = f.clean('13:30:05.000155')
+ self.assertEqual(result, time(13,30,5,155))
+
+ def test_localized_timeField(self):
+ "Localized TimeFields act as unlocalized widgets"
+ f = forms.TimeField(localize=True)
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13:30:05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13:30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+ def test_timeField_with_inputformat(self):
+ "TimeFields with manually specified input formats can accept those formats"
+ f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"])
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30.05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:05")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+ def test_localized_timeField_with_inputformat(self):
+ "Localized TimeFields with manually specified input formats can accept those formats"
+ f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"], localize=True)
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30.05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:05")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+
+@override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"])
+class CustomTimeInputFormatsTests(SimpleTestCase):
+ def test_timeField(self):
+ "TimeFields can parse dates in the default format"
+ f = forms.TimeField()
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '01:30:05 PM')
+
+ # Parse a time in a valid, but non-default format, get a parsed result
+ result = f.clean('1:30 PM')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM")
+
+ def test_localized_timeField(self):
+ "Localized TimeFields act as unlocalized widgets"
+ f = forms.TimeField(localize=True)
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '01:30:05 PM')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('01:30 PM')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM")
+
+ def test_timeField_with_inputformat(self):
+ "TimeFields with manually specified input formats can accept those formats"
+ f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"])
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30.05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:05 PM")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM")
+
+ def test_localized_timeField_with_inputformat(self):
+ "Localized TimeFields with manually specified input formats can accept those formats"
+ f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"], localize=True)
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30.05')
+ self.assertEqual(result, time(13,30,5))
+
+ # # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:05 PM")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13.30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM")
+
+
+class SimpleTimeFormatTests(SimpleTestCase):
+ def test_timeField(self):
+ "TimeFields can parse dates in the default format"
+ f = forms.TimeField()
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13:30:05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:05")
+
+ # Parse a time in a valid, but non-default format, get a parsed result
+ result = f.clean('13:30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+ def test_localized_timeField(self):
+ "Localized TimeFields in a non-localized environment act as unlocalized widgets"
+ f = forms.TimeField()
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13:30:05')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:05")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('13:30')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+ def test_timeField_with_inputformat(self):
+ "TimeFields with manually specified input formats can accept those formats"
+ f = forms.TimeField(input_formats=["%I:%M:%S %p", "%I:%M %p"])
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:05")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('1:30 PM')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+ def test_localized_timeField_with_inputformat(self):
+ "Localized TimeFields with manually specified input formats can accept those formats"
+ f = forms.TimeField(input_formats=["%I:%M:%S %p", "%I:%M %p"], localize=True)
+ # Parse a time in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05')
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM')
+ self.assertEqual(result, time(13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:05")
+
+ # Parse a time in a valid format, get a parsed result
+ result = f.clean('1:30 PM')
+ self.assertEqual(result, time(13,30,0))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "13:30:00")
+
+
+@override_settings(DATE_INPUT_FORMATS=["%d/%m/%Y", "%d-%m-%Y"], USE_L10N=True)
+class LocalizedDateTests(SimpleTestCase):
+ def setUp(self):
+ activate('de')
+
+ def tearDown(self):
+ deactivate()
+
+ def test_dateField(self):
+ "DateFields can parse dates in the default format"
+ f = forms.DateField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
+
+ # ISO formats are accepted, even if not specified in formats.py
+ self.assertEqual(f.clean('2010-12-21'), date(2010,12,21))
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '21.12.2010')
+
+ # Parse a date in a valid, but non-default format, get a parsed result
+ result = f.clean('21.12.10')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ def test_localized_dateField(self):
+ "Localized DateFields act as unlocalized widgets"
+ f = forms.DateField(localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.10')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ def test_dateField_with_inputformat(self):
+ "DateFields with manually specified input formats can accept those formats"
+ f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"])
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+ self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
+ self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12.21.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12-21-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ def test_localized_dateField_with_inputformat(self):
+ "Localized DateFields with manually specified input formats can accept those formats"
+ f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"], localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+ self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
+ self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12.21.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12-21-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+
+@override_settings(DATE_INPUT_FORMATS=["%d.%m.%Y", "%d-%m-%Y"])
+class CustomDateInputFormatsTests(SimpleTestCase):
+ def test_dateField(self):
+ "DateFields can parse dates in the default format"
+ f = forms.DateField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '21.12.2010')
+
+ # Parse a date in a valid, but non-default format, get a parsed result
+ result = f.clean('21-12-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ def test_localized_dateField(self):
+ "Localized DateFields act as unlocalized widgets"
+ f = forms.DateField(localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21-12-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ def test_dateField_with_inputformat(self):
+ "DateFields with manually specified input formats can accept those formats"
+ f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"])
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12.21.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12-21-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ def test_localized_dateField_with_inputformat(self):
+ "Localized DateFields with manually specified input formats can accept those formats"
+ f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"], localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12.21.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12-21-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010")
+
+class SimpleDateFormatTests(SimpleTestCase):
+ def test_dateField(self):
+ "DateFields can parse dates in the default format"
+ f = forms.DateField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('2010-12-21')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ # Parse a date in a valid, but non-default format, get a parsed result
+ result = f.clean('12/21/2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ def test_localized_dateField(self):
+ "Localized DateFields in a non-localized environment act as unlocalized widgets"
+ f = forms.DateField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('2010-12-21')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12/21/2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ def test_dateField_with_inputformat(self):
+ "DateFields with manually specified input formats can accept those formats"
+ f = forms.DateField(input_formats=["%d.%m.%Y", "%d-%m-%Y"])
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21-12-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ def test_localized_dateField_with_inputformat(self):
+ "Localized DateFields with manually specified input formats can accept those formats"
+ f = forms.DateField(input_formats=["%d.%m.%Y", "%d-%m-%Y"], localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21-12-2010')
+ self.assertEqual(result, date(2010,12,21))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21")
+
+
+@override_settings(DATETIME_INPUT_FORMATS=["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"], USE_L10N=True)
+class LocalizedDateTimeTests(SimpleTestCase):
+ def setUp(self):
+ activate('de')
+
+ def tearDown(self):
+ deactivate()
+
+ def test_dateTimeField(self):
+ "DateTimeFields can parse dates in the default format"
+ f = forms.DateTimeField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
+
+ # ISO formats are accepted, even if not specified in formats.py
+ self.assertEqual(f.clean('2010-12-21 13:30:05'), datetime(2010,12,21,13,30,5))
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '21.12.2010 13:30:05')
+
+ # Parse a date in a valid, but non-default format, get a parsed result
+ result = f.clean('21.12.2010 13:30')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010 13:30:00")
+
+ def test_localized_dateTimeField(self):
+ "Localized DateTimeFields act as unlocalized widgets"
+ f = forms.DateTimeField(localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '21.12.2010 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('21.12.2010 13:30')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010 13:30:00")
+
+ def test_dateTimeField_with_inputformat(self):
+ "DateTimeFields with manually specified input formats can accept those formats"
+ f = forms.DateTimeField(input_formats=["%H.%M.%S %m.%d.%Y", "%H.%M %m-%d-%Y"])
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05 13:30:05')
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('13.30.05 12.21.2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010 13:30:05")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('13.30 12-21-2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010 13:30:00")
+
+ def test_localized_dateTimeField_with_inputformat(self):
+ "Localized DateTimeFields with manually specified input formats can accept those formats"
+ f = forms.DateTimeField(input_formats=["%H.%M.%S %m.%d.%Y", "%H.%M %m-%d-%Y"], localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+ self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('13.30.05 12.21.2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010 13:30:05")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('13.30 12-21-2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "21.12.2010 13:30:00")
+
+
+@override_settings(DATETIME_INPUT_FORMATS=["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"])
+class CustomDateTimeInputFormatsTests(SimpleTestCase):
+ def test_dateTimeField(self):
+ "DateTimeFields can parse dates in the default format"
+ f = forms.DateTimeField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM 21/12/2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '01:30:05 PM 21/12/2010')
+
+ # Parse a date in a valid, but non-default format, get a parsed result
+ result = f.clean('1:30 PM 21-12-2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM 21/12/2010")
+
+ def test_localized_dateTimeField(self):
+ "Localized DateTimeFields act as unlocalized widgets"
+ f = forms.DateTimeField(localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM 21/12/2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, '01:30:05 PM 21/12/2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30 PM 21-12-2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM 21/12/2010")
+
+ def test_dateTimeField_with_inputformat(self):
+ "DateTimeFields with manually specified input formats can accept those formats"
+ f = forms.DateTimeField(input_formats=["%m.%d.%Y %H:%M:%S", "%m-%d-%Y %H:%M"])
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12.21.2010 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:05 PM 21/12/2010")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12-21-2010 13:30')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM 21/12/2010")
+
+ def test_localized_dateTimeField_with_inputformat(self):
+ "Localized DateTimeFields with manually specified input formats can accept those formats"
+ f = forms.DateTimeField(input_formats=["%m.%d.%Y %H:%M:%S", "%m-%d-%Y %H:%M"], localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12.21.2010 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:05 PM 21/12/2010")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12-21-2010 13:30')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "01:30:00 PM 21/12/2010")
+
+class SimpleDateTimeFormatTests(SimpleTestCase):
+ def test_dateTimeField(self):
+ "DateTimeFields can parse dates in the default format"
+ f = forms.DateTimeField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('2010-12-21 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:05")
+
+ # Parse a date in a valid, but non-default format, get a parsed result
+ result = f.clean('12/21/2010 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:05")
+
+ def test_localized_dateTimeField(self):
+ "Localized DateTimeFields in a non-localized environment act as unlocalized widgets"
+ f = forms.DateTimeField()
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('2010-12-21 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:05")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('12/21/2010 13:30:05')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:05")
+
+ def test_dateTimeField_with_inputformat(self):
+ "DateTimeFields with manually specified input formats can accept those formats"
+ f = forms.DateTimeField(input_formats=["%I:%M:%S %p %d.%m.%Y", "%I:%M %p %d-%m-%Y"])
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM 21.12.2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:05")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30 PM 21-12-2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:00")
+
+ def test_localized_dateTimeField_with_inputformat(self):
+ "Localized DateTimeFields with manually specified input formats can accept those formats"
+ f = forms.DateTimeField(input_formats=["%I:%M:%S %p %d.%m.%Y", "%I:%M %p %d-%m-%Y"], localize=True)
+ # Parse a date in an unaccepted format; get an error
+ self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05')
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30:05 PM 21.12.2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30,5))
+
+ # Check that the parsed result does a round trip to the same format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:05")
+
+ # Parse a date in a valid format, get a parsed result
+ result = f.clean('1:30 PM 21-12-2010')
+ self.assertEqual(result, datetime(2010,12,21,13,30))
+
+ # Check that the parsed result does a round trip to default format
+ text = f.widget._format_value(result)
+ self.assertEqual(text, "2010-12-21 13:30:00")
diff --git a/tests/forms_tests/tests/test_media.py b/tests/forms_tests/tests/test_media.py
new file mode 100644
index 0000000000..c492a1e539
--- /dev/null
+++ b/tests/forms_tests/tests/test_media.py
@@ -0,0 +1,907 @@
+# -*- coding: utf-8 -*-
+from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget
+from django.template import Template, Context
+from django.test import TestCase
+from django.test.utils import override_settings
+
+
+@override_settings(
+ STATIC_URL=None,
+ MEDIA_URL='http://media.example.com/media/',
+)
+class FormsMediaTestCase(TestCase):
+ """Tests for the media handling on widgets and forms"""
+
+ 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), """
+
+
+
+""")
+
+ 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), """
+
+
+
+""")
+
+ # 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), """
+
+
+
+""")
+
+ # Media objects can be interrogated by media type
+ self.assertEqual(str(w1.media['css']), """
+""")
+
+ self.assertEqual(str(w1.media['js']), """
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ # Check that media addition hasn't affected the original objects
+ self.assertEqual(str(w1.media), """
+
+
+
+""")
+
+ # 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), """
+""")
+
+ 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), """
+""")
+
+ # 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), """
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+
+""")
+
+ # 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+""")
+
+ # 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), """
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+
+""")
+
+ 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), """
+
+
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ # 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), """
+
+
+
+
+
+""")
+
+ # 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), """
+
+
+
+
+
+
+
+""")
+
+ # Media works in templates
+ self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """
+
+
+
+
+
+
+""")
+
+
+@override_settings(
+ STATIC_URL='http://media.example.com/static/',
+ MEDIA_URL='http://media.example.com/media/',
+)
+class StaticFormsMediaTestCase(TestCase):
+ """Tests for the media handling on widgets and forms"""
+
+ 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), """
+
+
+
+""")
+
+ 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), """
+
+
+
+""")
+
+ # 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), """
+
+
+
+""")
+
+ # Media objects can be interrogated by media type
+ self.assertEqual(str(w1.media['css']), """
+""")
+
+ self.assertEqual(str(w1.media['js']), """
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ # Check that media addition hasn't affected the original objects
+ self.assertEqual(str(w1.media), """
+
+
+
+""")
+
+ # 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), """
+""")
+
+ 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), """
+""")
+
+ # 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), """
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+
+""")
+
+ # 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+""")
+
+ # 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), """
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+
+""")
+
+ 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), """
+
+
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ 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), """
+
+
+
+
+
+""")
+
+ # 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), """
+
+
+
+
+
+""")
+
+ # 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), """
+
+
+
+
+
+
+
+""")
+
+ # Media works in templates
+ self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """
+
+
+
+
+
+
+""")
diff --git a/tests/forms_tests/tests/test_regressions.py b/tests/forms_tests/tests/test_regressions.py
new file mode 100644
index 0000000000..ba741bc16b
--- /dev/null
+++ b/tests/forms_tests/tests/test_regressions.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import warnings
+
+from django.forms import *
+from django.test import TestCase
+from django.utils.translation import ugettext_lazy, override
+
+from forms_tests.models import Cheese
+
+
+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.assertHTMLEqual(TestForm(auto_id=False).as_p(), '
F1:
\n
F2:
')
+
+ 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.assertHTMLEqual(f.as_p(), '
Username:
')
+
+ # Translations are done at rendering time, so multi-lingual apps can define forms)
+ with override('de'):
+ self.assertHTMLEqual(f.as_p(), '
Benutzername:
')
+ with override('pl', deactivate=True):
+ self.assertHTMLEqual(f.as_p(), '
Nazwa u\u017cytkownika:
')
+
+ 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.assertHTMLEqual(f['field_1'].label_tag(), 'field_1')
+ self.assertHTMLEqual(f['field_2'].label_tag(), 'field_2')
+
+ # Unicode decoding problems...
+ GENDERS = (('\xc5', 'En tied\xe4'), ('\xf8', 'Mies'), ('\xdf', 'Nainen'))
+
+ class SomeForm(Form):
+ somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label='\xc5\xf8\xdf')
+
+ f = SomeForm()
+ self.assertHTMLEqual(f.as_p(), '
\xc5\xf8\xdf:
\n
En tied\xe4
\n
Mies
\n
Nainen
\n
')
+
+ # Testing choice validation with UTF-8 bytestrings as input (these are the
+ # Russian abbreviations "мес." and "шт.".
+ UNITS = ((b'\xd0\xbc\xd0\xb5\xd1\x81.', b'\xd0\xbc\xd0\xb5\xd1\x81.'),
+ (b'\xd1\x88\xd1\x82.', b'\xd1\x88\xd1\x82.'))
+ f = ChoiceField(choices=UNITS)
+ with warnings.catch_warnings():
+ # Ignore UnicodeWarning
+ warnings.simplefilter("ignore")
+ self.assertEqual(f.clean('\u0448\u0442.'), '\u0448\u0442.')
+ self.assertEqual(f.clean(b'\xd1\x88\xd1\x82.'), '\u0448\u0442.')
+
+ # Translated error messages used to be buggy.
+ with override('ru'):
+ f = SomeForm({})
+ self.assertHTMLEqual(f.as_p(), '
')
+
+ # 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': '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.assertHTMLEqual(f.as_p(), '
(Hidden field data) This field is required.
\n
')
+ self.assertHTMLEqual(f.as_table(), '
(Hidden field data) This field is required.
')
+
+ 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': '