' % 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 regressiontests.forms.models import ChoiceModel
- c1 = ChoiceModel.objects.create(pk=1, name='a')
- c2 = ChoiceModel.objects.create(pk=2, name='b')
- c3 = ChoiceModel.objects.create(pk=3, name='c')
-
- # ModelChoiceField
- e = {
- 'required': 'REQUIRED',
- 'invalid_choice': 'INVALID CHOICE',
- }
- f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
- self.assertFormErrors(['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/extra.py b/tests/forms/tests/extra.py
deleted file mode 100644
index 359ad442bc..0000000000
--- a/tests/forms/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='Password (again)')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Your username:
-
Password1:
-
Password (again):
""")
-
- # 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(), """
""")
-
- # A label can be a Unicode object or a bytestring with special characters.
- class UserRegistration(Form):
- username = CharField(max_length=10, label='ŠĐĆŽćžšđ')
- password = CharField(widget=PasswordInput, label='\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), '
\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
\n
\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
')
-
- # 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_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='Choose wisely.')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
-
Password: Choose wisely.
""")
- self.assertHTMLEqual(p.as_p(), """
Username: e.g., user@example.com
-
Password: Choose wisely.
""")
- self.assertHTMLEqual(p.as_table(), """
Username:
e.g., user@example.com
-
Password:
Choose wisely.
""")
-
- # 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: Choose wisely.
""")
-
- # 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:
""")
-
- # Help text can include arbitrary Unicode characters.
- class UserRegistration(Form):
- username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ')
-
- p = UserRegistration(auto_id=False)
- self.assertHTMLEqual(p.as_ul(), '
')
-
- 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)})), '')
-
- # The label_tag() method takes an optional attrs argument: a dictionary of HTML
- # attributes to add to the tag.
- f = UserRegistration(auto_id='id_%s')
- form_output = []
-
- for bf in f:
- form_output.append(bf.label_tag(attrs={'class': 'pretty'}))
-
- expected_form_output = [
- 'Username',
- 'Password1',
- 'Password2',
- ]
- self.assertEqual(len(form_output), len(expected_form_output))
- for i in range(len(form_output)):
- self.assertHTMLEqual(form_output[i], expected_form_output[i])
-
- # 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'})
diff --git a/tests/forms/tests/formsets.py b/tests/forms/tests/formsets.py
deleted file mode 100644
index 2bef0c5c33..0000000000
--- a/tests/forms/tests/formsets.py
+++ /dev/null
@@ -1,1055 +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_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, even if extra and initial
- # are specified.
-
- initial = [
- {'name': 'Fernet and Coke'},
- {'name': 'Bloody Mary'},
- ]
- LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
- formset = LimitedFavoriteDrinkFormSet(initial=initial)
- form_output = []
-
- for form in formset.forms:
- form_output.append(str(form))
-
- self.assertEqual('\n'.join(form_output), "")
-
- def test_more_initial_than_max_num(self):
- # More initial forms than max_num will result in only the first max_num of
- # them to be displayed with no extra forms.
-
- initial = [
- {'name': 'Gin Tonic'},
- {'name': 'Bloody Mary'},
- {'name': 'Jack and Coke'},
- ]
- LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
- formset = LimitedFavoriteDrinkFormSet(initial=initial)
- form_output = []
-
- for form in formset.forms:
- form_output.append(str(form))
-
- self.assertHTMLEqual('\n'.join(form_output), """
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 = 3
- ChoiceFormSet = formset_factory(Choice)
- # 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)
- 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',
- )
- # This time four forms are instantiated
- 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/input_formats.py b/tests/forms/tests/input_formats.py
deleted file mode 100644
index 7a305dbc2b..0000000000
--- a/tests/forms/tests/input_formats.py
+++ /dev/null
@@ -1,861 +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
- 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")
-
- 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/media.py b/tests/forms/tests/media.py
deleted file mode 100644
index c492a1e539..0000000000
--- a/tests/forms/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/models.py b/tests/forms/tests/models.py
deleted file mode 100644
index be75643b28..0000000000
--- a/tests/forms/tests/models.py
+++ /dev/null
@@ -1,218 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
-import datetime
-
-from django.core.files.uploadedfile import SimpleUploadedFile
-from django.db import models
-from django.forms import Form, ModelForm, FileField, ModelChoiceField
-from django.forms.models import ModelFormMetaclass
-from django.test import TestCase
-from django.utils import six
-
-from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group,
- BoundaryModel, Defaults, OptionalMultiChoiceModel)
-
-
-class ChoiceFieldForm(ModelForm):
- class Meta:
- model = ChoiceFieldModel
-
-
-class OptionalMultiChoiceModelForm(ModelForm):
- class Meta:
- model = OptionalMultiChoiceModel
-
-
-class FileForm(Form):
- file1 = FileField()
-
-
-class TestTicket12510(TestCase):
- ''' It is not necessary to generate choices for ModelChoiceField (regression test for #12510). '''
- def setUp(self):
- self.groups = [Group.objects.create(name=name) for name in 'abc']
-
- def test_choices_not_fetched_when_not_rendering(self):
- # only one query is required to pull the model from DB
- with self.assertNumQueries(1):
- field = ModelChoiceField(Group.objects.order_by('-name'))
- self.assertEqual('a', field.clean(self.groups[0].pk).name)
-
-
-class TestTicket14567(TestCase):
- """
- Check that the return values of ModelMultipleChoiceFields are QuerySets
- """
- def test_empty_queryset_return(self):
- "If a model's ManyToManyField has blank=True and is saved with no data, a queryset is returned."
- form = OptionalMultiChoiceModelForm({'multi_choice_optional': '', 'multi_choice': ['1']})
- self.assertTrue(form.is_valid())
- # Check that the empty value is a QuerySet
- self.assertTrue(isinstance(form.cleaned_data['multi_choice_optional'], models.query.QuerySet))
- # While we're at it, test whether a QuerySet is returned if there *is* a value.
- self.assertTrue(isinstance(form.cleaned_data['multi_choice'], models.query.QuerySet))
-
-
-class ModelFormCallableModelDefault(TestCase):
- def test_no_empty_option(self):
- "If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)."
- option = ChoiceOptionModel.objects.create(name='default')
-
- choices = list(ChoiceFieldForm().fields['choice'].choices)
- self.assertEqual(len(choices), 1)
- self.assertEqual(choices[0], (option.pk, six.text_type(option)))
-
- def test_callable_initial_value(self):
- "The initial value for a callable default returning a queryset is the pk (refs #13769)"
- obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
- obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
- obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
- self.assertHTMLEqual(ChoiceFieldForm().as_p(), """
Choice:
-
Choice int:
-
Multi choice: Hold down "Control", or "Command" on a Mac, to select more than one.
-
Multi choice int: Hold down "Control", or "Command" on a Mac, to select more than one.
Multi choice:
- Hold down "Control", or "Command" on a Mac, to select more than one.
-
Multi choice int:
- Hold down "Control", or "Command" on a Mac, to select more than one.
""")
-
-
-class FormsModelTestCase(TestCase):
- def test_unicode_filename(self):
- # FileModel with unicode filename and data #########################
- f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False)
- self.assertTrue(f.is_valid())
- self.assertTrue('file1' in f.cleaned_data)
- m = FileModel.objects.create(file=f.cleaned_data['file1'])
- self.assertEqual(m.file.name, 'tests/\u6211\u96bb\u6c23\u588a\u8239\u88dd\u6eff\u6652\u9c54.txt')
- m.delete()
-
- def test_boundary_conditions(self):
- # Boundary conditions on a PostitiveIntegerField #########################
- class BoundaryForm(ModelForm):
- class Meta:
- model = BoundaryModel
-
- f = BoundaryForm({'positive_integer': 100})
- self.assertTrue(f.is_valid())
- f = BoundaryForm({'positive_integer': 0})
- self.assertTrue(f.is_valid())
- f = BoundaryForm({'positive_integer': -100})
- self.assertFalse(f.is_valid())
-
- def test_formfield_initial(self):
- # Formfield initial values ########
- # If the model has default values for some fields, they are used as the formfield
- # initial values.
- class DefaultsForm(ModelForm):
- class Meta:
- model = Defaults
-
- self.assertEqual(DefaultsForm().fields['name'].initial, 'class default value')
- self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))
- self.assertEqual(DefaultsForm().fields['value'].initial, 42)
- r1 = DefaultsForm()['callable_default'].as_widget()
- r2 = DefaultsForm()['callable_default'].as_widget()
- self.assertNotEqual(r1, r2)
-
- # In a ModelForm that is passed an instance, the initial values come from the
- # instance's values, not the model's defaults.
- foo_instance = Defaults(name='instance value', def_date=datetime.date(1969, 4, 4), value=12)
- instance_form = DefaultsForm(instance=foo_instance)
- self.assertEqual(instance_form.initial['name'], 'instance value')
- self.assertEqual(instance_form.initial['def_date'], datetime.date(1969, 4, 4))
- self.assertEqual(instance_form.initial['value'], 12)
-
- from django.forms import CharField
-
- class ExcludingForm(ModelForm):
- name = CharField(max_length=255)
-
- class Meta:
- model = Defaults
- exclude = ['name', 'callable_default']
-
- f = ExcludingForm({'name': 'Hello', 'value': 99, 'def_date': datetime.date(1999, 3, 2)})
- self.assertTrue(f.is_valid())
- self.assertEqual(f.cleaned_data['name'], 'Hello')
- obj = f.save()
- self.assertEqual(obj.name, 'class default value')
- self.assertEqual(obj.value, 99)
- self.assertEqual(obj.def_date, datetime.date(1999, 3, 2))
-
-class RelatedModelFormTests(TestCase):
- def test_invalid_loading_order(self):
- """
- Test for issue 10405
- """
- class A(models.Model):
- ref = models.ForeignKey("B")
-
- class Meta:
- model=A
-
- self.assertRaises(ValueError, ModelFormMetaclass, str('Form'), (ModelForm,), {'Meta': Meta})
-
- class B(models.Model):
- pass
-
- def test_valid_loading_order(self):
- """
- Test for issue 10405
- """
- class A(models.Model):
- ref = models.ForeignKey("B")
-
- class B(models.Model):
- pass
-
- class Meta:
- model=A
-
- self.assertTrue(issubclass(ModelFormMetaclass(str('Form'), (ModelForm,), {'Meta': Meta}), ModelForm))
diff --git a/tests/forms/tests/regressions.py b/tests/forms/tests/regressions.py
deleted file mode 100644
index 45f51eef2f..0000000000
--- a/tests/forms/tests/regressions.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from warnings import catch_warnings
-
-from django.forms import *
-from django.test import TestCase
-from django.utils.translation import ugettext_lazy, override
-
-from regressiontests.forms.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)
- self.assertEqual(f.clean('\u0448\u0442.'), '\u0448\u0442.')
- with catch_warnings(record=True):
- # Ignore UnicodeWarning
- 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='Password (again)')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Your username:
+
Password1:
+
Password (again):
""")
+
+ # 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(), """
""")
+
+ # A label can be a Unicode object or a bytestring with special characters.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, label='ŠĐĆŽćžšđ')
+ password = CharField(widget=PasswordInput, label='\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), '
\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
\n
\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
')
+
+ # 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_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='Choose wisely.')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), """
Username: e.g., user@example.com
+
Password: Choose wisely.
""")
+ self.assertHTMLEqual(p.as_p(), """
Username: e.g., user@example.com
+
Password: Choose wisely.
""")
+ self.assertHTMLEqual(p.as_table(), """
Username:
e.g., user@example.com
+
Password:
Choose wisely.
""")
+
+ # 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: Choose wisely.
""")
+
+ # 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:
""")
+
+ # Help text can include arbitrary Unicode characters.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ')
+
+ p = UserRegistration(auto_id=False)
+ self.assertHTMLEqual(p.as_ul(), '
')
+
+ 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)})), '')
+
+ # The label_tag() method takes an optional attrs argument: a dictionary of HTML
+ # attributes to add to the tag.
+ f = UserRegistration(auto_id='id_%s')
+ form_output = []
+
+ for bf in f:
+ form_output.append(bf.label_tag(attrs={'class': 'pretty'}))
+
+ expected_form_output = [
+ 'Username',
+ 'Password1',
+ 'Password2',
+ ]
+ self.assertEqual(len(form_output), len(expected_form_output))
+ for i in range(len(form_output)):
+ self.assertHTMLEqual(form_output[i], expected_form_output[i])
+
+ # 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'})
diff --git a/tests/forms_tests/tests/formsets.py b/tests/forms_tests/tests/formsets.py
new file mode 100644
index 0000000000..2bef0c5c33
--- /dev/null
+++ b/tests/forms_tests/tests/formsets.py
@@ -0,0 +1,1055 @@
+# -*- 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_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, even if extra and initial
+ # are specified.
+
+ initial = [
+ {'name': 'Fernet and Coke'},
+ {'name': 'Bloody Mary'},
+ ]
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
+ formset = LimitedFavoriteDrinkFormSet(initial=initial)
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), "")
+
+ def test_more_initial_than_max_num(self):
+ # More initial forms than max_num will result in only the first max_num of
+ # them to be displayed with no extra forms.
+
+ initial = [
+ {'name': 'Gin Tonic'},
+ {'name': 'Bloody Mary'},
+ {'name': 'Jack and Coke'},
+ ]
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
+ formset = LimitedFavoriteDrinkFormSet(initial=initial)
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertHTMLEqual('\n'.join(form_output), """
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 = 3
+ ChoiceFormSet = formset_factory(Choice)
+ # 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)
+ 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',
+ )
+ # This time four forms are instantiated
+ 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
new file mode 100644
index 0000000000..7a305dbc2b
--- /dev/null
+++ b/tests/forms_tests/tests/input_formats.py
@@ -0,0 +1,861 @@
+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
+ 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")
+
+ 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
new file mode 100644
index 0000000000..c492a1e539
--- /dev/null
+++ b/tests/forms_tests/tests/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/models.py b/tests/forms_tests/tests/models.py
new file mode 100644
index 0000000000..be75643b28
--- /dev/null
+++ b/tests/forms_tests/tests/models.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals
+
+import datetime
+
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.db import models
+from django.forms import Form, ModelForm, FileField, ModelChoiceField
+from django.forms.models import ModelFormMetaclass
+from django.test import TestCase
+from django.utils import six
+
+from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group,
+ BoundaryModel, Defaults, OptionalMultiChoiceModel)
+
+
+class ChoiceFieldForm(ModelForm):
+ class Meta:
+ model = ChoiceFieldModel
+
+
+class OptionalMultiChoiceModelForm(ModelForm):
+ class Meta:
+ model = OptionalMultiChoiceModel
+
+
+class FileForm(Form):
+ file1 = FileField()
+
+
+class TestTicket12510(TestCase):
+ ''' It is not necessary to generate choices for ModelChoiceField (regression test for #12510). '''
+ def setUp(self):
+ self.groups = [Group.objects.create(name=name) for name in 'abc']
+
+ def test_choices_not_fetched_when_not_rendering(self):
+ # only one query is required to pull the model from DB
+ with self.assertNumQueries(1):
+ field = ModelChoiceField(Group.objects.order_by('-name'))
+ self.assertEqual('a', field.clean(self.groups[0].pk).name)
+
+
+class TestTicket14567(TestCase):
+ """
+ Check that the return values of ModelMultipleChoiceFields are QuerySets
+ """
+ def test_empty_queryset_return(self):
+ "If a model's ManyToManyField has blank=True and is saved with no data, a queryset is returned."
+ form = OptionalMultiChoiceModelForm({'multi_choice_optional': '', 'multi_choice': ['1']})
+ self.assertTrue(form.is_valid())
+ # Check that the empty value is a QuerySet
+ self.assertTrue(isinstance(form.cleaned_data['multi_choice_optional'], models.query.QuerySet))
+ # While we're at it, test whether a QuerySet is returned if there *is* a value.
+ self.assertTrue(isinstance(form.cleaned_data['multi_choice'], models.query.QuerySet))
+
+
+class ModelFormCallableModelDefault(TestCase):
+ def test_no_empty_option(self):
+ "If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)."
+ option = ChoiceOptionModel.objects.create(name='default')
+
+ choices = list(ChoiceFieldForm().fields['choice'].choices)
+ self.assertEqual(len(choices), 1)
+ self.assertEqual(choices[0], (option.pk, six.text_type(option)))
+
+ def test_callable_initial_value(self):
+ "The initial value for a callable default returning a queryset is the pk (refs #13769)"
+ obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
+ obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
+ obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
+ self.assertHTMLEqual(ChoiceFieldForm().as_p(), """
Choice:
+
Choice int:
+
Multi choice: Hold down "Control", or "Command" on a Mac, to select more than one.
+
Multi choice int: Hold down "Control", or "Command" on a Mac, to select more than one.
Multi choice:
+ Hold down "Control", or "Command" on a Mac, to select more than one.
+
Multi choice int:
+ Hold down "Control", or "Command" on a Mac, to select more than one.
""")
+
+
+class FormsModelTestCase(TestCase):
+ def test_unicode_filename(self):
+ # FileModel with unicode filename and data #########################
+ f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))}, auto_id=False)
+ self.assertTrue(f.is_valid())
+ self.assertTrue('file1' in f.cleaned_data)
+ m = FileModel.objects.create(file=f.cleaned_data['file1'])
+ self.assertEqual(m.file.name, 'tests/\u6211\u96bb\u6c23\u588a\u8239\u88dd\u6eff\u6652\u9c54.txt')
+ m.delete()
+
+ def test_boundary_conditions(self):
+ # Boundary conditions on a PostitiveIntegerField #########################
+ class BoundaryForm(ModelForm):
+ class Meta:
+ model = BoundaryModel
+
+ f = BoundaryForm({'positive_integer': 100})
+ self.assertTrue(f.is_valid())
+ f = BoundaryForm({'positive_integer': 0})
+ self.assertTrue(f.is_valid())
+ f = BoundaryForm({'positive_integer': -100})
+ self.assertFalse(f.is_valid())
+
+ def test_formfield_initial(self):
+ # Formfield initial values ########
+ # If the model has default values for some fields, they are used as the formfield
+ # initial values.
+ class DefaultsForm(ModelForm):
+ class Meta:
+ model = Defaults
+
+ self.assertEqual(DefaultsForm().fields['name'].initial, 'class default value')
+ self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))
+ self.assertEqual(DefaultsForm().fields['value'].initial, 42)
+ r1 = DefaultsForm()['callable_default'].as_widget()
+ r2 = DefaultsForm()['callable_default'].as_widget()
+ self.assertNotEqual(r1, r2)
+
+ # In a ModelForm that is passed an instance, the initial values come from the
+ # instance's values, not the model's defaults.
+ foo_instance = Defaults(name='instance value', def_date=datetime.date(1969, 4, 4), value=12)
+ instance_form = DefaultsForm(instance=foo_instance)
+ self.assertEqual(instance_form.initial['name'], 'instance value')
+ self.assertEqual(instance_form.initial['def_date'], datetime.date(1969, 4, 4))
+ self.assertEqual(instance_form.initial['value'], 12)
+
+ from django.forms import CharField
+
+ class ExcludingForm(ModelForm):
+ name = CharField(max_length=255)
+
+ class Meta:
+ model = Defaults
+ exclude = ['name', 'callable_default']
+
+ f = ExcludingForm({'name': 'Hello', 'value': 99, 'def_date': datetime.date(1999, 3, 2)})
+ self.assertTrue(f.is_valid())
+ self.assertEqual(f.cleaned_data['name'], 'Hello')
+ obj = f.save()
+ self.assertEqual(obj.name, 'class default value')
+ self.assertEqual(obj.value, 99)
+ self.assertEqual(obj.def_date, datetime.date(1999, 3, 2))
+
+class RelatedModelFormTests(TestCase):
+ def test_invalid_loading_order(self):
+ """
+ Test for issue 10405
+ """
+ class A(models.Model):
+ ref = models.ForeignKey("B")
+
+ class Meta:
+ model=A
+
+ self.assertRaises(ValueError, ModelFormMetaclass, str('Form'), (ModelForm,), {'Meta': Meta})
+
+ class B(models.Model):
+ pass
+
+ def test_valid_loading_order(self):
+ """
+ Test for issue 10405
+ """
+ class A(models.Model):
+ ref = models.ForeignKey("B")
+
+ class B(models.Model):
+ pass
+
+ class Meta:
+ model=A
+
+ self.assertTrue(issubclass(ModelFormMetaclass(str('Form'), (ModelForm,), {'Meta': Meta}), ModelForm))
diff --git a/tests/forms_tests/tests/regressions.py b/tests/forms_tests/tests/regressions.py
new file mode 100644
index 0000000000..ecee92402e
--- /dev/null
+++ b/tests/forms_tests/tests/regressions.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from warnings import catch_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)
+ self.assertEqual(f.clean('\u0448\u0442.'), '\u0448\u0442.')
+ with catch_warnings(record=True):
+ # Ignore UnicodeWarning
+ 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': '"}, ""),
+ 'filter-urlize06': ('{{ a|urlize }}', {"a": ""}, '<script>alert('foo')</script>'),
+
+ # mailto: testing for urlize
+ 'filter-urlize07': ('{{ a|urlize }}', {"a": "Email me at me@example.com"}, 'Email me at me@example.com'),
+ 'filter-urlize08': ('{{ a|urlize }}', {"a": "Email me at "}, 'Email me at <me@example.com>'),
+
+ 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, '"Unsafe" http:... "Safe" http:...'),
+ 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, '"Unsafe" http:... "Safe" http:...'),
+
+ 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"),
+ 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"),
+
+ 'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"),
+ 'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"),
+
+ 'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."),
+ 'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."),
+
+ 'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."),
+ 'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."),
+
+ 'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."),
+ 'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."),
+
+ 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"),
+ 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"),
+ 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"),
+ 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"),
+ # Passing ';' to cut can break existing HTML entities, so those strings
+ # are auto-escaped.
+ 'filter-cut05': ('{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"),
+ 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"),
+
+ # The "escape" filter works the same whether autoescape is on or off,
+ # but it has no effect on strings already marked as safe.
+ 'filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"),
+ 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"),
+
+ # It is only applied once, regardless of the number of times it
+ # appears in a chain.
+ 'filter-escape03': ('{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"),
+ 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&y"),
+
+ # Force_escape is applied immediately. It can be used to provide
+ # double-escaping, for example.
+ 'filter-force-escape01': ('{% autoescape off %}{{ a|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"),
+ 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, "x&y"),
+ 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"),
+ 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&y"),
+
+ # Because the result of force_escape is "safe", an additional
+ # escape filter has no effect.
+ 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"),
+ 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, "x&y"),
+ 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"),
+ 'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, "x&y"),
+
+ # The contents in "linebreaks" and "linebreaksbr" are escaped
+ # according to the current autoescape setting.
+ 'filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "
', status_code=403)
+ finally:
+ restore_template_loaders()
+
+ def test_403_template(self):
+ # Set up a test 403.html template.
+ setup_test_template_loader(
+ {'403.html': 'This is a test template for a 403 Forbidden error.'}
+ )
+ try:
+ response = self.client.get('/views/raises403/')
+ self.assertContains(response, 'test template', status_code=403)
+ finally:
+ restore_template_loaders()
+
+ def test_404(self):
+ response = self.client.get('/views/raises404/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_view_exceptions(self):
+ for n in range(len(except_args)):
+ self.assertRaises(BrokenException, self.client.get,
+ reverse('view_exception', args=(n,)))
+
+ def test_template_exceptions(self):
+ for n in range(len(except_args)):
+ try:
+ self.client.get(reverse('template_exception', args=(n,)))
+ except Exception:
+ raising_loc = inspect.trace()[-1][-2][0].strip()
+ self.assertFalse(raising_loc.find('raise BrokenException') == -1,
+ "Failed to find 'raise BrokenException' in last frame of traceback, instead found: %s" %
+ raising_loc)
+
+ def test_template_loader_postmortem(self):
+ response = self.client.get(reverse('raises_template_does_not_exist'))
+ template_path = os.path.join('templates', 'i_dont_exist.html')
+ self.assertContains(response, template_path, status_code=500)
+
+
+class ExceptionReporterTests(TestCase):
+ rf = RequestFactory()
+
+ def test_request_and_exception(self):
+ "A simple exception report can be generated"
+ try:
+ request = self.rf.get('/test_view/')
+ raise ValueError("Can't find my keys")
+ except ValueError:
+ exc_type, exc_value, tb = sys.exc_info()
+ reporter = ExceptionReporter(request, exc_type, exc_value, tb)
+ html = reporter.get_traceback_html()
+ self.assertIn('
ValueError at /test_view/
', html)
+ self.assertIn('
Can't find my keys
', html)
+ self.assertIn('
Request Method:
', html)
+ self.assertIn('
Request URL:
', html)
+ self.assertIn('
Exception Type:
', html)
+ self.assertIn('
Exception Value:
', html)
+ self.assertIn('
Traceback ', html)
+ self.assertIn('
Request information
', html)
+ self.assertNotIn('
Request data not supplied
', html)
+
+ def test_no_request(self):
+ "An exception report can be generated without request"
+ try:
+ raise ValueError("Can't find my keys")
+ except ValueError:
+ exc_type, exc_value, tb = sys.exc_info()
+ reporter = ExceptionReporter(None, exc_type, exc_value, tb)
+ html = reporter.get_traceback_html()
+ self.assertIn('
ValueError
', html)
+ self.assertIn('
Can't find my keys
', html)
+ self.assertNotIn('
Request Method:
', html)
+ self.assertNotIn('
Request URL:
', html)
+ self.assertIn('
Exception Type:
', html)
+ self.assertIn('
Exception Value:
', html)
+ self.assertIn('
Traceback ', html)
+ self.assertIn('
Request information
', html)
+ self.assertIn('
Request data not supplied
', html)
+
+ def test_no_exception(self):
+ "An exception report can be generated for just a request"
+ request = self.rf.get('/test_view/')
+ reporter = ExceptionReporter(request, None, None, None)
+ html = reporter.get_traceback_html()
+ self.assertIn('
Report at /test_view/
', html)
+ self.assertIn('
No exception supplied
', html)
+ self.assertIn('
Request Method:
', html)
+ self.assertIn('
Request URL:
', html)
+ self.assertNotIn('
Exception Type:
', html)
+ self.assertNotIn('
Exception Value:
', html)
+ self.assertNotIn('
Traceback ', html)
+ self.assertIn('
Request information
', html)
+ self.assertNotIn('
Request data not supplied
', html)
+
+ def test_request_and_message(self):
+ "A message can be provided in addition to a request"
+ request = self.rf.get('/test_view/')
+ reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
+ html = reporter.get_traceback_html()
+ self.assertIn('
Report at /test_view/
', html)
+ self.assertIn('
I'm a little teapot
', html)
+ self.assertIn('
Request Method:
', html)
+ self.assertIn('
Request URL:
', html)
+ self.assertNotIn('
Exception Type:
', html)
+ self.assertNotIn('
Exception Value:
', html)
+ self.assertNotIn('
Traceback ', html)
+ self.assertIn('
Request information
', html)
+ self.assertNotIn('
Request data not supplied
', html)
+
+ def test_message_only(self):
+ reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
+ html = reporter.get_traceback_html()
+ self.assertIn('
Report
', html)
+ self.assertIn('
I'm a little teapot
', html)
+ self.assertNotIn('
Request Method:
', html)
+ self.assertNotIn('
Request URL:
', html)
+ self.assertNotIn('
Exception Type:
', html)
+ self.assertNotIn('
Exception Value:
', html)
+ self.assertNotIn('
Traceback ', html)
+ self.assertIn('
Request information
', html)
+ self.assertIn('
Request data not supplied
', html)
+
+
+class PlainTextReportTests(TestCase):
+ rf = RequestFactory()
+
+ def test_request_and_exception(self):
+ "A simple exception report can be generated"
+ try:
+ request = self.rf.get('/test_view/')
+ raise ValueError("Can't find my keys")
+ except ValueError:
+ exc_type, exc_value, tb = sys.exc_info()
+ reporter = ExceptionReporter(request, exc_type, exc_value, tb)
+ text = reporter.get_traceback_text()
+ self.assertIn('ValueError at /test_view/', text)
+ self.assertIn("Can't find my keys", text)
+ self.assertIn('Request Method:', text)
+ self.assertIn('Request URL:', text)
+ self.assertIn('Exception Type:', text)
+ self.assertIn('Exception Value:', text)
+ self.assertIn('Traceback:', text)
+ self.assertIn('Request information:', text)
+ self.assertNotIn('Request data not supplied', text)
+
+ def test_no_request(self):
+ "An exception report can be generated without request"
+ try:
+ raise ValueError("Can't find my keys")
+ except ValueError:
+ exc_type, exc_value, tb = sys.exc_info()
+ reporter = ExceptionReporter(None, exc_type, exc_value, tb)
+ text = reporter.get_traceback_text()
+ self.assertIn('ValueError', text)
+ self.assertIn("Can't find my keys", text)
+ self.assertNotIn('Request Method:', text)
+ self.assertNotIn('Request URL:', text)
+ self.assertIn('Exception Type:', text)
+ self.assertIn('Exception Value:', text)
+ self.assertIn('Traceback:', text)
+ self.assertIn('Request data not supplied', text)
+
+ def test_no_exception(self):
+ "An exception report can be generated for just a request"
+ request = self.rf.get('/test_view/')
+ reporter = ExceptionReporter(request, None, None, None)
+ text = reporter.get_traceback_text()
+
+ def test_request_and_message(self):
+ "A message can be provided in addition to a request"
+ request = self.rf.get('/test_view/')
+ reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
+ text = reporter.get_traceback_text()
+
+ def test_message_only(self):
+ reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
+ text = reporter.get_traceback_text()
+
+
+class ExceptionReportTestMixin(object):
+
+ # Mixin used in the ExceptionReporterFilterTests and
+ # AjaxResponseExceptionReporterFilter tests below
+
+ breakfast_data = {'sausage-key': 'sausage-value',
+ 'baked-beans-key': 'baked-beans-value',
+ 'hash-brown-key': 'hash-brown-value',
+ 'bacon-key': 'bacon-value',}
+
+ def verify_unsafe_response(self, view, check_for_vars=True,
+ check_for_POST_params=True):
+ """
+ Asserts that potentially sensitive info are displayed in the response.
+ """
+ request = self.rf.post('/some_url/', self.breakfast_data)
+ response = view(request)
+ if check_for_vars:
+ # All variables are shown.
+ self.assertContains(response, 'cooked_eggs', status_code=500)
+ self.assertContains(response, 'scrambled', status_code=500)
+ self.assertContains(response, 'sauce', status_code=500)
+ self.assertContains(response, 'worcestershire', status_code=500)
+ if check_for_POST_params:
+ for k, v in self.breakfast_data.items():
+ # All POST parameters are shown.
+ self.assertContains(response, k, status_code=500)
+ self.assertContains(response, v, status_code=500)
+
+ def verify_safe_response(self, view, check_for_vars=True,
+ check_for_POST_params=True):
+ """
+ Asserts that certain sensitive info are not displayed in the response.
+ """
+ request = self.rf.post('/some_url/', self.breakfast_data)
+ response = view(request)
+ if check_for_vars:
+ # Non-sensitive variable's name and value are shown.
+ self.assertContains(response, 'cooked_eggs', status_code=500)
+ self.assertContains(response, 'scrambled', status_code=500)
+ # Sensitive variable's name is shown but not its value.
+ self.assertContains(response, 'sauce', status_code=500)
+ self.assertNotContains(response, 'worcestershire', status_code=500)
+ if check_for_POST_params:
+ for k, v in self.breakfast_data.items():
+ # All POST parameters' names are shown.
+ self.assertContains(response, k, status_code=500)
+ # Non-sensitive POST parameters' values are shown.
+ self.assertContains(response, 'baked-beans-value', status_code=500)
+ self.assertContains(response, 'hash-brown-value', status_code=500)
+ # Sensitive POST parameters' values are not shown.
+ self.assertNotContains(response, 'sausage-value', status_code=500)
+ self.assertNotContains(response, 'bacon-value', status_code=500)
+
+ def verify_paranoid_response(self, view, check_for_vars=True,
+ check_for_POST_params=True):
+ """
+ Asserts that no variables or POST parameters are displayed in the response.
+ """
+ request = self.rf.post('/some_url/', self.breakfast_data)
+ response = view(request)
+ if check_for_vars:
+ # Show variable names but not their values.
+ self.assertContains(response, 'cooked_eggs', status_code=500)
+ self.assertNotContains(response, 'scrambled', status_code=500)
+ self.assertContains(response, 'sauce', status_code=500)
+ self.assertNotContains(response, 'worcestershire', status_code=500)
+ if check_for_POST_params:
+ for k, v in self.breakfast_data.items():
+ # All POST parameters' names are shown.
+ self.assertContains(response, k, status_code=500)
+ # No POST parameters' values are shown.
+ self.assertNotContains(response, v, status_code=500)
+
+ def verify_unsafe_email(self, view, check_for_POST_params=True):
+ """
+ Asserts that potentially sensitive info are displayed in the email report.
+ """
+ with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+ mail.outbox = [] # Empty outbox
+ request = self.rf.post('/some_url/', self.breakfast_data)
+ response = view(request)
+ self.assertEqual(len(mail.outbox), 1)
+ email = mail.outbox[0]
+
+ # Frames vars are never shown in plain text email reports.
+ body_plain = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body_plain)
+ self.assertNotIn('scrambled', body_plain)
+ self.assertNotIn('sauce', body_plain)
+ self.assertNotIn('worcestershire', body_plain)
+
+ # Frames vars are shown in html email reports.
+ body_html = force_text(email.alternatives[0][0])
+ self.assertIn('cooked_eggs', body_html)
+ self.assertIn('scrambled', body_html)
+ self.assertIn('sauce', body_html)
+ self.assertIn('worcestershire', body_html)
+
+ if check_for_POST_params:
+ for k, v in self.breakfast_data.items():
+ # All POST parameters are shown.
+ self.assertIn(k, body_plain)
+ self.assertIn(v, body_plain)
+ self.assertIn(k, body_html)
+ self.assertIn(v, body_html)
+
+ def verify_safe_email(self, view, check_for_POST_params=True):
+ """
+ Asserts that certain sensitive info are not displayed in the email report.
+ """
+ with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+ mail.outbox = [] # Empty outbox
+ request = self.rf.post('/some_url/', self.breakfast_data)
+ response = view(request)
+ self.assertEqual(len(mail.outbox), 1)
+ email = mail.outbox[0]
+
+ # Frames vars are never shown in plain text email reports.
+ body_plain = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body_plain)
+ self.assertNotIn('scrambled', body_plain)
+ self.assertNotIn('sauce', body_plain)
+ self.assertNotIn('worcestershire', body_plain)
+
+ # Frames vars are shown in html email reports.
+ body_html = force_text(email.alternatives[0][0])
+ self.assertIn('cooked_eggs', body_html)
+ self.assertIn('scrambled', body_html)
+ self.assertIn('sauce', body_html)
+ self.assertNotIn('worcestershire', body_html)
+
+ if check_for_POST_params:
+ for k, v in self.breakfast_data.items():
+ # All POST parameters' names are shown.
+ self.assertIn(k, body_plain)
+ # Non-sensitive POST parameters' values are shown.
+ self.assertIn('baked-beans-value', body_plain)
+ self.assertIn('hash-brown-value', body_plain)
+ self.assertIn('baked-beans-value', body_html)
+ self.assertIn('hash-brown-value', body_html)
+ # Sensitive POST parameters' values are not shown.
+ self.assertNotIn('sausage-value', body_plain)
+ self.assertNotIn('bacon-value', body_plain)
+ self.assertNotIn('sausage-value', body_html)
+ self.assertNotIn('bacon-value', body_html)
+
+ def verify_paranoid_email(self, view):
+ """
+ Asserts that no variables or POST parameters are displayed in the email report.
+ """
+ with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+ mail.outbox = [] # Empty outbox
+ request = self.rf.post('/some_url/', self.breakfast_data)
+ response = view(request)
+ self.assertEqual(len(mail.outbox), 1)
+ email = mail.outbox[0]
+ # Frames vars are never shown in plain text email reports.
+ body = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body)
+ self.assertNotIn('scrambled', body)
+ self.assertNotIn('sauce', body)
+ self.assertNotIn('worcestershire', body)
+ for k, v in self.breakfast_data.items():
+ # All POST parameters' names are shown.
+ self.assertIn(k, body)
+ # No POST parameters' values are shown.
+ self.assertNotIn(v, body)
+
+
+class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin):
+ """
+ Ensure that sensitive information can be filtered out of error reports.
+ Refs #14614.
+ """
+ rf = RequestFactory()
+
+ def test_non_sensitive_request(self):
+ """
+ Ensure that everything (request info and frame variables) can bee seen
+ in the default error reports for non-sensitive requests.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(non_sensitive_view)
+ self.verify_unsafe_email(non_sensitive_view)
+
+ with self.settings(DEBUG=False):
+ self.verify_unsafe_response(non_sensitive_view)
+ self.verify_unsafe_email(non_sensitive_view)
+
+ def test_sensitive_request(self):
+ """
+ Ensure that sensitive POST parameters and frame variables cannot be
+ seen in the default error reports for sensitive requests.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_view)
+ self.verify_unsafe_email(sensitive_view)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_view)
+ self.verify_safe_email(sensitive_view)
+
+ def test_paranoid_request(self):
+ """
+ Ensure that no POST parameters and frame variables can be seen in the
+ default error reports for "paranoid" requests.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(paranoid_view)
+ self.verify_unsafe_email(paranoid_view)
+
+ with self.settings(DEBUG=False):
+ self.verify_paranoid_response(paranoid_view)
+ self.verify_paranoid_email(paranoid_view)
+
+ def test_custom_exception_reporter_filter(self):
+ """
+ Ensure that it's possible to assign an exception reporter filter to
+ the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(custom_exception_reporter_filter_view)
+ self.verify_unsafe_email(custom_exception_reporter_filter_view)
+
+ with self.settings(DEBUG=False):
+ self.verify_unsafe_response(custom_exception_reporter_filter_view)
+ self.verify_unsafe_email(custom_exception_reporter_filter_view)
+
+ def test_sensitive_method(self):
+ """
+ Ensure that the sensitive_variables decorator works with object
+ methods.
+ Refs #18379.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_method_view,
+ check_for_POST_params=False)
+ self.verify_unsafe_email(sensitive_method_view,
+ check_for_POST_params=False)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_method_view,
+ check_for_POST_params=False)
+ self.verify_safe_email(sensitive_method_view,
+ check_for_POST_params=False)
+
+ def test_sensitive_function_arguments(self):
+ """
+ Ensure that sensitive variables don't leak in the sensitive_variables
+ decorator's frame, when those variables are passed as arguments to the
+ decorated function.
+ Refs #19453.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_args_function_caller)
+ self.verify_unsafe_email(sensitive_args_function_caller)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_args_function_caller, check_for_POST_params=False)
+ self.verify_safe_email(sensitive_args_function_caller, check_for_POST_params=False)
+
+ def test_sensitive_function_keyword_arguments(self):
+ """
+ Ensure that sensitive variables don't leak in the sensitive_variables
+ decorator's frame, when those variables are passed as keyword arguments
+ to the decorated function.
+ Refs #19453.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_kwargs_function_caller)
+ self.verify_unsafe_email(sensitive_kwargs_function_caller)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_kwargs_function_caller, check_for_POST_params=False)
+ self.verify_safe_email(sensitive_kwargs_function_caller, check_for_POST_params=False)
+
+
+class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin):
+ """
+ Ensure that sensitive information can be filtered out of error reports.
+
+ Here we specifically test the plain text 500 debug-only error page served
+ when it has been detected the request was sent by JS code. We don't check
+ for (non)existence of frames vars in the traceback information section of
+ the response content because we don't include them in these error pages.
+ Refs #14614.
+ """
+ rf = RequestFactory(HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+
+ def test_non_sensitive_request(self):
+ """
+ Ensure that request info can bee seen in the default error reports for
+ non-sensitive requests.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
+
+ with self.settings(DEBUG=False):
+ self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
+
+ def test_sensitive_request(self):
+ """
+ Ensure that sensitive POST parameters cannot be seen in the default
+ error reports for sensitive requests.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_view, check_for_vars=False)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_view, check_for_vars=False)
+
+ def test_paranoid_request(self):
+ """
+ Ensure that no POST parameters can be seen in the default error reports
+ for "paranoid" requests.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(paranoid_view, check_for_vars=False)
+
+ with self.settings(DEBUG=False):
+ self.verify_paranoid_response(paranoid_view, check_for_vars=False)
+
+ def test_custom_exception_reporter_filter(self):
+ """
+ Ensure that it's possible to assign an exception reporter filter to
+ the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(custom_exception_reporter_filter_view,
+ check_for_vars=False)
+
+ with self.settings(DEBUG=False):
+ self.verify_unsafe_response(custom_exception_reporter_filter_view,
+ check_for_vars=False)
diff --git a/tests/view_tests/tests/defaults.py b/tests/view_tests/tests/defaults.py
new file mode 100644
index 0000000000..3ca7f79136
--- /dev/null
+++ b/tests/view_tests/tests/defaults.py
@@ -0,0 +1,98 @@
+from __future__ import absolute_import, unicode_literals
+
+from django.contrib.contenttypes.models import ContentType
+from django.test import TestCase
+from django.test.utils import setup_test_template_loader, restore_template_loaders
+
+from ..models import Author, Article, UrlArticle
+
+
+class DefaultsTests(TestCase):
+ """Test django views in django/views/defaults.py"""
+ fixtures = ['testdata.json']
+ non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
+ '/views/other_non_existing_url/'] # this NOT in urls.py
+
+ def test_shortcut_with_absolute_url(self):
+ "Can view a shortcut for an Author object that has a get_absolute_url method"
+ for obj in Author.objects.all():
+ short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk)
+ response = self.client.get(short_url)
+ self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(),
+ status_code=302, target_status_code=404)
+
+ def test_shortcut_no_absolute_url(self):
+ "Shortcuts for an object that has no get_absolute_url method raises 404"
+ for obj in Article.objects.all():
+ short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk)
+ response = self.client.get(short_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_wrong_type_pk(self):
+ short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects')
+ response = self.client.get(short_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_shortcut_bad_pk(self):
+ short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242')
+ response = self.client.get(short_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_nonint_content_type(self):
+ an_author = Author.objects.all()[0]
+ short_url = '/views/shortcut/%s/%s/' % ('spam', an_author.pk)
+ response = self.client.get(short_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_bad_content_type(self):
+ an_author = Author.objects.all()[0]
+ short_url = '/views/shortcut/%s/%s/' % (42424242, an_author.pk)
+ response = self.client.get(short_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_page_not_found(self):
+ "A 404 status is returned by the page_not_found view"
+ for url in self.non_existing_urls:
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_csrf_token_in_404(self):
+ """
+ The 404 page should have the csrf_token available in the context
+ """
+ # See ticket #14565
+ for url in self.non_existing_urls:
+ response = self.client.get(url)
+ csrf_token = response.context['csrf_token']
+ self.assertNotEqual(str(csrf_token), 'NOTPROVIDED')
+ self.assertNotEqual(str(csrf_token), '')
+
+ def test_server_error(self):
+ "The server_error view raises a 500 status"
+ response = self.client.get('/views/server_error/')
+ self.assertEqual(response.status_code, 500)
+
+ def test_custom_templates(self):
+ """
+ Test that 404.html and 500.html templates are picked by their respective
+ handler.
+ """
+ setup_test_template_loader(
+ {'404.html': 'This is a test template for a 404 error.',
+ '500.html': 'This is a test template for a 500 error.'}
+ )
+ try:
+ for code, url in ((404, '/views/non_existing_url/'), (500, '/views/server_error/')):
+ response = self.client.get(url)
+ self.assertContains(response, "test template for a %d error" % code,
+ status_code=code)
+ finally:
+ restore_template_loaders()
+
+ def test_get_absolute_url_attributes(self):
+ "A model can set attributes on the get_absolute_url method"
+ self.assertTrue(getattr(UrlArticle.get_absolute_url, 'purge', False),
+ 'The attributes of the original get_absolute_url must be added.')
+ article = UrlArticle.objects.get(pk=1)
+ self.assertTrue(getattr(article.get_absolute_url, 'purge', False),
+ 'The attributes of the original get_absolute_url must be added.')
diff --git a/tests/view_tests/tests/i18n.py b/tests/view_tests/tests/i18n.py
new file mode 100644
index 0000000000..d186fab335
--- /dev/null
+++ b/tests/view_tests/tests/i18n.py
@@ -0,0 +1,221 @@
+# -*- coding:utf-8 -*-
+from __future__ import absolute_import
+
+import gettext
+import os
+from os import path
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.test import LiveServerTestCase, TestCase
+from django.test.utils import override_settings
+from django.utils import six, unittest
+from django.utils._os import upath
+from django.utils.translation import override
+from django.utils.text import javascript_quote
+
+try:
+ from selenium.webdriver.firefox import webdriver as firefox
+except ImportError:
+ firefox = None
+
+from ..urls import locale_dir
+
+
+class I18NTests(TestCase):
+ """ Tests django views in django/views/i18n.py """
+
+ def test_setlang(self):
+ """
+ The set_language view can be used to change the session language.
+
+ The user is redirected to the 'next' argument if provided.
+ """
+ for lang_code, lang_name in settings.LANGUAGES:
+ post_data = dict(language=lang_code, next='/views/')
+ response = self.client.post('/views/i18n/setlang/', data=post_data)
+ self.assertRedirects(response, 'http://testserver/views/')
+ self.assertEqual(self.client.session['django_language'], lang_code)
+
+ def test_setlang_unsafe_next(self):
+ """
+ The set_language view only redirects to the 'next' argument if it is
+ "safe".
+ """
+ lang_code, lang_name = settings.LANGUAGES[0]
+ post_data = dict(language=lang_code, next='//unsafe/redirection/')
+ response = self.client.post('/views/i18n/setlang/', data=post_data)
+ self.assertEqual(response.url, 'http://testserver/')
+ self.assertEqual(self.client.session['django_language'], lang_code)
+
+ def test_setlang_reversal(self):
+ self.assertEqual(reverse('set_language'), '/views/i18n/setlang/')
+
+ def test_jsi18n(self):
+ """The javascript_catalog can be deployed with language settings"""
+ for lang_code in ['es', 'fr', 'ru']:
+ with override(lang_code):
+ catalog = gettext.translation('djangojs', locale_dir, [lang_code])
+ if six.PY3:
+ trans_txt = catalog.gettext('this is to be translated')
+ else:
+ trans_txt = catalog.ugettext('this is to be translated')
+ response = self.client.get('/views/jsi18n/')
+ # in response content must to be a line like that:
+ # catalog['this is to be translated'] = 'same_that_trans_txt'
+ # javascript_quote is used to be able to check unicode strings
+ self.assertContains(response, javascript_quote(trans_txt), 1)
+ if lang_code == 'fr':
+ # Message with context (msgctxt)
+ self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
+
+
+class JsI18NTests(TestCase):
+ """
+ Tests django views in django/views/i18n.py that need to change
+ settings.LANGUAGE_CODE.
+ """
+
+ def test_jsi18n_with_missing_en_files(self):
+ """
+ The javascript_catalog shouldn't load the fallback language in the
+ case that the current selected language is actually the one translated
+ from, and hence missing translation files completely.
+
+ This happens easily when you're translating from English to other
+ languages and you've set settings.LANGUAGE_CODE to some other language
+ than English.
+ """
+ with self.settings(LANGUAGE_CODE='es'):
+ with override('en-us'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertNotContains(response, 'esto tiene que ser traducido')
+
+ def test_jsi18n_fallback_language(self):
+ """
+ Let's make sure that the fallback language is still working properly
+ in cases where the selected language cannot be found.
+ """
+ with self.settings(LANGUAGE_CODE='fr'):
+ with override('fi'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertContains(response, 'il faut le traduire')
+
+ def testI18NLanguageNonEnglishDefault(self):
+ """
+ Check if the Javascript i18n view returns an empty language catalog
+ if the default language is non-English, the selected language
+ is English and there is not 'en' translation available. See #13388,
+ #3594 and #13726 for more details.
+ """
+ with self.settings(LANGUAGE_CODE='fr'):
+ with override('en-us'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertNotContains(response, 'Choisir une heure')
+
+ def test_nonenglish_default_english_userpref(self):
+ """
+ Same as above with the difference that there IS an 'en' translation
+ available. The Javascript i18n view must return a NON empty language catalog
+ with the proper English translations. See #13726 for more details.
+ """
+ extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app0']
+ with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps):
+ with override('en-us'):
+ response = self.client.get('/views/jsi18n_english_translation/')
+ self.assertContains(response, javascript_quote('this app0 string is to be translated'))
+
+ def testI18NLanguageNonEnglishFallback(self):
+ """
+ Makes sure that the fallback language is still working properly
+ in cases where the selected language cannot be found.
+ """
+ with self.settings(LANGUAGE_CODE='fr'):
+ with override('none'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertContains(response, 'Choisir une heure')
+
+
+class JsI18NTestsMultiPackage(TestCase):
+ """
+ Tests for django views in django/views/i18n.py that need to change
+ settings.LANGUAGE_CODE and merge JS translation from several packages.
+ """
+ def testI18NLanguageEnglishDefault(self):
+ """
+ Check if the JavaScript i18n view returns a complete language catalog
+ if the default language is en-us, the selected language has a
+ translation available and a catalog composed by djangojs domain
+ translations of multiple Python packages is requested. See #13388,
+ #3594 and #13514 for more details.
+ """
+ extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app1', 'view_tests.app2']
+ with self.settings(LANGUAGE_CODE='en-us', INSTALLED_APPS=extended_apps):
+ with override('fr'):
+ response = self.client.get('/views/jsi18n_multi_packages1/')
+ self.assertContains(response, javascript_quote('il faut traduire cette chaîne de caractères de app1'))
+
+ def testI18NDifferentNonEnLangs(self):
+ """
+ Similar to above but with neither default or requested language being
+ English.
+ """
+ extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app3', 'view_tests.app4']
+ with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps):
+ with override('es-ar'):
+ response = self.client.get('/views/jsi18n_multi_packages2/')
+ self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))
+
+ def testI18NWithLocalePaths(self):
+ extended_locale_paths = settings.LOCALE_PATHS + (
+ path.join(path.dirname(
+ path.dirname(path.abspath(upath(__file__)))), 'app3', 'locale'),)
+ with self.settings(LANGUAGE_CODE='es-ar', LOCALE_PATHS=extended_locale_paths):
+ with override('es-ar'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertContains(response,
+ javascript_quote('este texto de app3 debe ser traducido'))
+
+
+skip_selenium = not os.environ.get('DJANGO_SELENIUM_TESTS', False)
+
+
+@unittest.skipIf(skip_selenium, 'Selenium tests not requested')
+@unittest.skipUnless(firefox, 'Selenium not installed')
+class JavascriptI18nTests(LiveServerTestCase):
+ urls = 'view_tests.urls'
+
+ @classmethod
+ def setUpClass(cls):
+ cls.selenium = firefox.WebDriver()
+ super(JavascriptI18nTests, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.selenium.quit()
+ super(JavascriptI18nTests, cls).tearDownClass()
+
+ @override_settings(LANGUAGE_CODE='de')
+ def test_javascript_gettext(self):
+ extended_apps = list(settings.INSTALLED_APPS) + ['view_tests']
+ with self.settings(INSTALLED_APPS=extended_apps):
+ self.selenium.get('%s%s' % (self.live_server_url, '/jsi18n_template/'))
+
+ elem = self.selenium.find_element_by_id("gettext")
+ self.assertEqual(elem.text, "Entfernen")
+ elem = self.selenium.find_element_by_id("ngettext_sing")
+ self.assertEqual(elem.text, "1 Element")
+ elem = self.selenium.find_element_by_id("ngettext_plur")
+ self.assertEqual(elem.text, "455 Elemente")
+ elem = self.selenium.find_element_by_id("pgettext")
+ self.assertEqual(elem.text, "Kann")
+ elem = self.selenium.find_element_by_id("npgettext_sing")
+ self.assertEqual(elem.text, "1 Resultat")
+ elem = self.selenium.find_element_by_id("npgettext_plur")
+ self.assertEqual(elem.text, "455 Resultate")
+
+ def test_escaping(self):
+ extended_apps = list(settings.INSTALLED_APPS) + ['view_tests']
+ with self.settings(INSTALLED_APPS=extended_apps):
+ response = self.client.get('%s%s' % (self.live_server_url, '/jsi18n_admin/'))
+ self.assertContains(response, '\\x04')
diff --git a/tests/view_tests/tests/shortcuts.py b/tests/view_tests/tests/shortcuts.py
new file mode 100644
index 0000000000..74c556f41a
--- /dev/null
+++ b/tests/view_tests/tests/shortcuts.py
@@ -0,0 +1,59 @@
+from django.conf import settings
+from django.test import TestCase
+from django.test.utils import override_settings
+
+@override_settings(
+ TEMPLATE_CONTEXT_PROCESSORS=('django.core.context_processors.static',),
+ STATIC_URL='/path/to/static/media/',
+)
+class ShortcutTests(TestCase):
+ urls = 'view_tests.generic_urls'
+
+ def test_render_to_response(self):
+ response = self.client.get('/shortcuts/render_to_response/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, b'FOO.BAR..\n')
+ self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
+
+ def test_render_to_response_with_request_context(self):
+ response = self.client.get('/shortcuts/render_to_response/request_context/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
+ self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
+
+ def test_render_to_response_with_content_type(self):
+ response = self.client.get('/shortcuts/render_to_response/content_type/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, b'FOO.BAR..\n')
+ self.assertEqual(response['Content-Type'], 'application/x-rendertest')
+
+ def test_render(self):
+ response = self.client.get('/shortcuts/render/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
+ self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
+ self.assertEqual(response.context.current_app, None)
+
+ def test_render_with_base_context(self):
+ response = self.client.get('/shortcuts/render/base_context/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, b'FOO.BAR..\n')
+ self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
+
+ def test_render_with_content_type(self):
+ response = self.client.get('/shortcuts/render/content_type/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
+ self.assertEqual(response['Content-Type'], 'application/x-rendertest')
+
+ def test_render_with_status(self):
+ response = self.client.get('/shortcuts/render/status/')
+ self.assertEqual(response.status_code, 403)
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
+
+ def test_render_with_current_app(self):
+ response = self.client.get('/shortcuts/render/current_app/')
+ self.assertEqual(response.context.current_app, "foobar_app")
+
+ def test_render_with_current_app_conflict(self):
+ self.assertRaises(ValueError, self.client.get, '/shortcuts/render/current_app_conflict/')
diff --git a/tests/view_tests/tests/specials.py b/tests/view_tests/tests/specials.py
new file mode 100644
index 0000000000..fc1dd98c0e
--- /dev/null
+++ b/tests/view_tests/tests/specials.py
@@ -0,0 +1,39 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from django.test import TestCase
+
+
+class URLHandling(TestCase):
+ """
+ Tests for URL handling in views and responses.
+ """
+ urls = 'view_tests.generic_urls'
+ redirect_target = "/%E4%B8%AD%E6%96%87/target/"
+
+ def test_combining_redirect(self):
+ """
+ Tests that redirecting to an IRI, requiring encoding before we use it
+ in an HTTP response, is handled correctly. In this case the arg to
+ HttpRedirect is ASCII but the current request path contains non-ASCII
+ characters so this test ensures the creation of the full path with a
+ base non-ASCII part is handled correctly.
+ """
+ response = self.client.get('/中文/')
+ self.assertRedirects(response, self.redirect_target)
+
+ def test_nonascii_redirect(self):
+ """
+ Tests that a non-ASCII argument to HttpRedirect is handled properly.
+ """
+ response = self.client.get('/nonascii_redirect/')
+ self.assertRedirects(response, self.redirect_target)
+
+ def test_permanent_nonascii_redirect(self):
+ """
+ Tests that a non-ASCII argument to HttpPermanentRedirect is handled
+ properly.
+ """
+ response = self.client.get('/permanent_nonascii_redirect/')
+ self.assertRedirects(response, self.redirect_target, status_code=301)
+
diff --git a/tests/view_tests/tests/static.py b/tests/view_tests/tests/static.py
new file mode 100644
index 0000000000..bdd9fbfc0b
--- /dev/null
+++ b/tests/view_tests/tests/static.py
@@ -0,0 +1,120 @@
+from __future__ import absolute_import
+
+import mimetypes
+from os import path
+import unittest
+
+from django.conf.urls.static import static
+from django.http import HttpResponseNotModified
+from django.test import TestCase
+from django.test.utils import override_settings
+from django.utils.http import http_date
+from django.views.static import was_modified_since
+
+from .. import urls
+from ..urls import media_dir
+
+
+@override_settings(DEBUG=True)
+class StaticTests(TestCase):
+ """Tests django views in django/views/static.py"""
+
+ prefix = 'site_media'
+
+ def test_serve(self):
+ "The static view can serve static media"
+ media_files = ['file.txt', 'file.txt.gz']
+ for filename in media_files:
+ response = self.client.get('/views/%s/%s' % (self.prefix, filename))
+ response_content = b''.join(response)
+ file_path = path.join(media_dir, filename)
+ with open(file_path, 'rb') as fp:
+ self.assertEqual(fp.read(), response_content)
+ self.assertEqual(len(response_content), int(response['Content-Length']))
+ self.assertEqual(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))
+
+ def test_unknown_mime_type(self):
+ response = self.client.get('/views/%s/file.unknown' % self.prefix)
+ self.assertEqual('application/octet-stream', response['Content-Type'])
+
+ def test_copes_with_empty_path_component(self):
+ file_name = 'file.txt'
+ response = self.client.get('/views/%s//%s' % (self.prefix, file_name))
+ response_content = b''.join(response)
+ with open(path.join(media_dir, file_name), 'rb') as fp:
+ self.assertEqual(fp.read(), response_content)
+
+ def test_is_modified_since(self):
+ file_name = 'file.txt'
+ response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
+ HTTP_IF_MODIFIED_SINCE='Thu, 1 Jan 1970 00:00:00 GMT')
+ response_content = b''.join(response)
+ with open(path.join(media_dir, file_name), 'rb') as fp:
+ self.assertEqual(fp.read(), response_content)
+
+ def test_not_modified_since(self):
+ file_name = 'file.txt'
+ response = self.client.get(
+ '/views/%s/%s' % (self.prefix, file_name),
+ HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 GMT'
+ # This is 24h before max Unix time. Remember to fix Django and
+ # update this test well before 2038 :)
+ )
+ self.assertTrue(isinstance(response, HttpResponseNotModified))
+
+ def test_invalid_if_modified_since(self):
+ """Handle bogus If-Modified-Since values gracefully
+
+ Assume that a file is modified since an invalid timestamp as per RFC
+ 2616, section 14.25.
+ """
+ file_name = 'file.txt'
+ invalid_date = 'Mon, 28 May 999999999999 28:25:26 GMT'
+ response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
+ HTTP_IF_MODIFIED_SINCE=invalid_date)
+ response_content = b''.join(response)
+ with open(path.join(media_dir, file_name), 'rb') as fp:
+ self.assertEqual(fp.read(), response_content)
+ self.assertEqual(len(response_content),
+ int(response['Content-Length']))
+
+ def test_invalid_if_modified_since2(self):
+ """Handle even more bogus If-Modified-Since values gracefully
+
+ Assume that a file is modified since an invalid timestamp as per RFC
+ 2616, section 14.25.
+ """
+ file_name = 'file.txt'
+ invalid_date = ': 1291108438, Wed, 20 Oct 2010 14:05:00 GMT'
+ response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
+ HTTP_IF_MODIFIED_SINCE=invalid_date)
+ response_content = b''.join(response)
+ with open(path.join(media_dir, file_name), 'rb') as fp:
+ self.assertEqual(fp.read(), response_content)
+ self.assertEqual(len(response_content),
+ int(response['Content-Length']))
+
+
+class StaticHelperTest(StaticTests):
+ """
+ Test case to make sure the static URL pattern helper works as expected
+ """
+ def setUp(self):
+ super(StaticHelperTest, self).setUp()
+ self._old_views_urlpatterns = urls.urlpatterns[:]
+ urls.urlpatterns += static('/media/', document_root=media_dir)
+
+ def tearDown(self):
+ super(StaticHelperTest, self).tearDown()
+ urls.urlpatterns = self._old_views_urlpatterns
+
+
+class StaticUtilsTests(unittest.TestCase):
+ def test_was_modified_since_fp(self):
+ """
+ Test that a floating point mtime does not disturb was_modified_since.
+ (#18675)
+ """
+ mtime = 1343416141.107817
+ header = http_date(mtime)
+ self.assertFalse(was_modified_since(header, mtime))
diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py
new file mode 100644
index 0000000000..8a3b492cec
--- /dev/null
+++ b/tests/view_tests/urls.py
@@ -0,0 +1,71 @@
+# coding: utf-8
+from __future__ import absolute_import
+
+from os import path
+
+from django.conf.urls import patterns, url, include
+from django.utils._os import upath
+
+from . import views
+
+
+base_dir = path.dirname(path.abspath(upath(__file__)))
+media_dir = path.join(base_dir, 'media')
+locale_dir = path.join(base_dir, 'locale')
+
+js_info_dict = {
+ 'domain': 'djangojs',
+ 'packages': ('view_tests',),
+}
+
+js_info_dict_english_translation = {
+ 'domain': 'djangojs',
+ 'packages': ('view_tests.app0',),
+}
+
+js_info_dict_multi_packages1 = {
+ 'domain': 'djangojs',
+ 'packages': ('view_tests.app1', 'view_tests.app2'),
+}
+
+js_info_dict_multi_packages2 = {
+ 'domain': 'djangojs',
+ 'packages': ('view_tests.app3', 'view_tests.app4'),
+}
+
+js_info_dict_admin = {
+ 'domain': 'djangojs',
+ 'packages': ('django.contrib.admin', 'view_tests'),
+}
+
+urlpatterns = patterns('',
+ (r'^$', views.index_page),
+
+ # Default views
+ (r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
+ (r'^non_existing_url/', 'django.views.defaults.page_not_found'),
+ (r'^server_error/', 'django.views.defaults.server_error'),
+
+ # a view that raises an exception for the debug view
+ (r'raises/$', views.raises),
+ (r'raises404/$', views.raises404),
+ (r'raises403/$', views.raises403),
+
+ # i18n views
+ (r'^i18n/', include('django.conf.urls.i18n')),
+ (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
+ (r'^jsi18n_english_translation/$', 'django.views.i18n.javascript_catalog', js_info_dict_english_translation),
+ (r'^jsi18n_multi_packages1/$', 'django.views.i18n.javascript_catalog', js_info_dict_multi_packages1),
+ (r'^jsi18n_multi_packages2/$', 'django.views.i18n.javascript_catalog', js_info_dict_multi_packages2),
+ (r'^jsi18n_admin/$', 'django.views.i18n.javascript_catalog', js_info_dict_admin),
+ (r'^jsi18n_template/$', views.jsi18n),
+
+ # Static views
+ (r'^site_media/(?P.*)$', 'django.views.static.serve', {'document_root': media_dir}),
+)
+
+urlpatterns += patterns('view_tests.views',
+ url(r'view_exception/(?P\d+)/$', 'view_exception', name='view_exception'),
+ url(r'template_exception/(?P\d+)/$', 'template_exception', name='template_exception'),
+ url(r'^raises_template_does_not_exist/$', 'raises_template_does_not_exist', name='raises_template_does_not_exist'),
+)
diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py
new file mode 100644
index 0000000000..50ad98ac2d
--- /dev/null
+++ b/tests/view_tests/views.py
@@ -0,0 +1,266 @@
+from __future__ import absolute_import
+
+import sys
+
+from django.core.exceptions import PermissionDenied
+from django.core.urlresolvers import get_resolver
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response, render
+from django.template import Context, RequestContext, TemplateDoesNotExist
+from django.views.debug import technical_500_response, SafeExceptionReporterFilter
+from django.views.decorators.debug import (sensitive_post_parameters,
+ sensitive_variables)
+from django.utils.log import getLogger
+
+from . import BrokenException, except_args
+
+
+
+def index_page(request):
+ """Dummy index page"""
+ return HttpResponse('Dummy page')
+
+def raises(request):
+ # Make sure that a callable that raises an exception in the stack frame's
+ # local vars won't hijack the technical 500 response. See:
+ # http://code.djangoproject.com/ticket/15025
+ def callable():
+ raise Exception
+ try:
+ raise Exception
+ except Exception:
+ return technical_500_response(request, *sys.exc_info())
+
+def raises404(request):
+ resolver = get_resolver(None)
+ resolver.resolve('')
+
+def raises403(request):
+ raise PermissionDenied
+
+def redirect(request):
+ """
+ Forces an HTTP redirect.
+ """
+ return HttpResponseRedirect("target/")
+
+def view_exception(request, n):
+ raise BrokenException(except_args[int(n)])
+
+def template_exception(request, n):
+ return render_to_response('debug/template_exception.html',
+ {'arg': except_args[int(n)]})
+
+def jsi18n(request):
+ return render_to_response('jsi18n.html')
+
+# Some views to exercise the shortcuts
+
+def render_to_response_view(request):
+ return render_to_response('debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ })
+
+def render_to_response_view_with_request_context(request):
+ return render_to_response('debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, context_instance=RequestContext(request))
+
+def render_to_response_view_with_content_type(request):
+ return render_to_response('debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, content_type='application/x-rendertest')
+
+def render_view(request):
+ return render(request, 'debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ })
+
+def render_view_with_base_context(request):
+ return render(request, 'debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, context_instance=Context())
+
+def render_view_with_content_type(request):
+ return render(request, 'debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, content_type='application/x-rendertest')
+
+def render_view_with_status(request):
+ return render(request, 'debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, status=403)
+
+def render_view_with_current_app(request):
+ return render(request, 'debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, current_app="foobar_app")
+
+def render_view_with_current_app_conflict(request):
+ # This should fail because we don't passing both a current_app and
+ # context_instance:
+ return render(request, 'debug/render_test.html', {
+ 'foo': 'FOO',
+ 'bar': 'BAR',
+ }, current_app="foobar_app", context_instance=RequestContext(request))
+
+def raises_template_does_not_exist(request):
+ # We need to inspect the HTML generated by the fancy 500 debug view but
+ # the test client ignores it, so we send it explicitly.
+ try:
+ return render_to_response('i_dont_exist.html')
+ except TemplateDoesNotExist:
+ return technical_500_response(request, *sys.exc_info())
+
+def send_log(request, exc_info):
+ logger = getLogger('django.request')
+ # The default logging config has a logging filter to ensure admin emails are
+ # only sent with DEBUG=False, but since someone might choose to remove that
+ # filter, we still want to be able to test the behavior of error emails
+ # with DEBUG=True. So we need to remove the filter temporarily.
+ admin_email_handler = [
+ h for h in logger.handlers
+ if h.__class__.__name__ == "AdminEmailHandler"
+ ][0]
+ orig_filters = admin_email_handler.filters
+ admin_email_handler.filters = []
+ admin_email_handler.include_html = True
+ logger.error('Internal Server Error: %s', request.path,
+ exc_info=exc_info,
+ extra={
+ 'status_code': 500,
+ 'request': request
+ }
+ )
+ admin_email_handler.filters = orig_filters
+
+def non_sensitive_view(request):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+ try:
+ raise Exception
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+@sensitive_variables('sauce')
+@sensitive_post_parameters('bacon-key', 'sausage-key')
+def sensitive_view(request):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+ try:
+ raise Exception
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+@sensitive_variables()
+@sensitive_post_parameters()
+def paranoid_view(request):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+ try:
+ raise Exception
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+def sensitive_args_function_caller(request):
+ try:
+ sensitive_args_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']))
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+@sensitive_variables('sauce')
+def sensitive_args_function(sauce):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ raise Exception
+
+def sensitive_kwargs_function_caller(request):
+ try:
+ sensitive_kwargs_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']))
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+@sensitive_variables('sauce')
+def sensitive_kwargs_function(sauce=None):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ raise Exception
+
+class UnsafeExceptionReporterFilter(SafeExceptionReporterFilter):
+ """
+ Ignores all the filtering done by its parent class.
+ """
+
+ def get_post_parameters(self, request):
+ return request.POST
+
+ def get_traceback_frame_variables(self, request, tb_frame):
+ return tb_frame.f_locals.items()
+
+
+@sensitive_variables()
+@sensitive_post_parameters()
+def custom_exception_reporter_filter_view(request):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+ request.exception_reporter_filter = UnsafeExceptionReporterFilter()
+ try:
+ raise Exception
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+
+class Klass(object):
+
+ @sensitive_variables('sauce')
+ def method(self, request):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's
+ # source is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+ try:
+ raise Exception
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+def sensitive_method_view(request):
+ return Klass().method(request)
diff --git a/tests/views/__init__.py b/tests/views/__init__.py
deleted file mode 100644
index 1193ffe010..0000000000
--- a/tests/views/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-class BrokenException(Exception):
- pass
-
-except_args = (b'Broken!', # plain exception with ASCII text
- '¡Broken!', # non-ASCII unicode data
- '¡Broken!'.encode('utf-8'), # non-ASCII, utf-8 encoded bytestring
- b'\xa1Broken!', ) # non-ASCII, latin1 bytestring
-
diff --git a/tests/views/app0/__init__.py b/tests/views/app0/__init__.py
deleted file mode 100644
index 792d600548..0000000000
--- a/tests/views/app0/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#
diff --git a/tests/views/app0/locale/en/LC_MESSAGES/djangojs.mo b/tests/views/app0/locale/en/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 662204a6d7..0000000000
Binary files a/tests/views/app0/locale/en/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/app0/locale/en/LC_MESSAGES/djangojs.po b/tests/views/app0/locale/en/LC_MESSAGES/djangojs.po
deleted file mode 100644
index a458935f96..0000000000
--- a/tests/views/app0/locale/en/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,20 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 19:15+0200\n"
-"PO-Revision-Date: 2010-05-12 12:41-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "il faut traduire cette chaîne de caractères de app0"
-msgstr "this app0 string is to be translated"
diff --git a/tests/views/app1/__init__.py b/tests/views/app1/__init__.py
deleted file mode 100644
index 792d600548..0000000000
--- a/tests/views/app1/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#
diff --git a/tests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo b/tests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 5d6aecb446..0000000000
Binary files a/tests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/app1/locale/fr/LC_MESSAGES/djangojs.po b/tests/views/app1/locale/fr/LC_MESSAGES/djangojs.po
deleted file mode 100644
index a4627db877..0000000000
--- a/tests/views/app1/locale/fr/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,20 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 19:15+0200\n"
-"PO-Revision-Date: 2010-05-12 12:41-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "this app1 string is to be translated"
-msgstr "il faut traduire cette chaîne de caractères de app1"
diff --git a/tests/views/app2/__init__.py b/tests/views/app2/__init__.py
deleted file mode 100644
index 792d600548..0000000000
--- a/tests/views/app2/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#
diff --git a/tests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo b/tests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 17e1863672..0000000000
Binary files a/tests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/app2/locale/fr/LC_MESSAGES/djangojs.po b/tests/views/app2/locale/fr/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 637b9e6bcd..0000000000
--- a/tests/views/app2/locale/fr/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,20 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 19:15+0200\n"
-"PO-Revision-Date: 2010-05-12 22:05-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "this app2 string is to be translated"
-msgstr "il faut traduire cette chaîne de caractères de app2"
diff --git a/tests/views/app3/__init__.py b/tests/views/app3/__init__.py
deleted file mode 100644
index 792d600548..0000000000
--- a/tests/views/app3/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#
diff --git a/tests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo b/tests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 0c485a9838..0000000000
Binary files a/tests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po b/tests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 1e3be0bfea..0000000000
--- a/tests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,20 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 19:15+0200\n"
-"PO-Revision-Date: 2010-05-12 12:41-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "il faut traduire cette chaîne de caractères de app3"
-msgstr "este texto de app3 debe ser traducido"
diff --git a/tests/views/app4/__init__.py b/tests/views/app4/__init__.py
deleted file mode 100644
index 792d600548..0000000000
--- a/tests/views/app4/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#
diff --git a/tests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo b/tests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 581fbb0347..0000000000
Binary files a/tests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po b/tests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 27403c0783..0000000000
--- a/tests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,20 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 19:15+0200\n"
-"PO-Revision-Date: 2010-05-12 12:41-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "il faut traduire cette chaîne de caractères de app4"
-msgstr "este texto de app4 debe ser traducido"
diff --git a/tests/views/fixtures/testdata.json b/tests/views/fixtures/testdata.json
deleted file mode 100644
index ab68407ab6..0000000000
--- a/tests/views/fixtures/testdata.json
+++ /dev/null
@@ -1,75 +0,0 @@
-[
- {
- "pk": "1",
- "model": "auth.user",
- "fields": {
- "username": "testclient",
- "first_name": "Test",
- "last_name": "Client",
- "is_active": true,
- "is_superuser": false,
- "is_staff": false,
- "last_login": "2006-12-17 07:03:31",
- "groups": [],
- "user_permissions": [],
- "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
- "email": "testclient@example.com",
- "date_joined": "2006-12-17 07:03:31"
- }
- },
- {
- "pk": 1,
- "model": "views.author",
- "fields": {
- "name": "Boris"
- }
- },
- {
- "pk": 1,
- "model": "views.article",
- "fields": {
- "author": 1,
- "title": "Old Article",
- "slug": "old_article",
- "date_created": "2001-01-01 21:22:23"
- }
- },
- {
- "pk": 2,
- "model": "views.article",
- "fields": {
- "author": 1,
- "title": "Current Article",
- "slug": "current_article",
- "date_created": "2007-09-17 21:22:23"
- }
- },
- {
- "pk": 3,
- "model": "views.article",
- "fields": {
- "author": 1,
- "title": "Future Article",
- "slug": "future_article",
- "date_created": "3000-01-01 21:22:23"
- }
- },
- {
- "pk": 1,
- "model": "views.urlarticle",
- "fields": {
- "author": 1,
- "title": "Old Article",
- "slug": "old_article",
- "date_created": "2001-01-01 21:22:23"
- }
- },
- {
- "pk": 1,
- "model": "sites.site",
- "fields": {
- "domain": "testserver",
- "name": "testserver"
- }
- }
-]
diff --git a/tests/views/generic_urls.py b/tests/views/generic_urls.py
deleted file mode 100644
index d50436279a..0000000000
--- a/tests/views/generic_urls.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding:utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
-from django.conf.urls import patterns, url
-from django.views.generic import RedirectView
-
-from . import views
-from .models import Article, DateArticle, UrlArticle
-
-
-date_based_info_dict = {
- 'queryset': Article.objects.all(),
- 'date_field': 'date_created',
- 'month_format': '%m',
-}
-
-object_list_dict = {
- 'queryset': Article.objects.all(),
- 'paginate_by': 2,
-}
-
-object_list_no_paginate_by = {
- 'queryset': Article.objects.all(),
-}
-
-numeric_days_info_dict = dict(date_based_info_dict, day_format='%d')
-
-date_based_datefield_info_dict = dict(date_based_info_dict, queryset=DateArticle.objects.all())
-
-urlpatterns = patterns('',
- (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
- (r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
-
- # Special URLs for particular regression cases.
- url('^中文/$', 'regressiontests.views.views.redirect'),
- url('^中文/target/$', 'regressiontests.views.views.index_page'),
-)
-
-# rediriects, both temporary and permanent, with non-ASCII targets
-urlpatterns += patterns('',
- ('^nonascii_redirect/$', RedirectView.as_view(
- url='/中文/target/', permanent=False)),
- ('^permanent_nonascii_redirect/$', RedirectView.as_view(
- url='/中文/target/', permanent=True)),
-)
-
-urlpatterns += patterns('regressiontests.views.views',
- (r'^shortcuts/render_to_response/$', 'render_to_response_view'),
- (r'^shortcuts/render_to_response/request_context/$', 'render_to_response_view_with_request_context'),
- (r'^shortcuts/render_to_response/content_type/$', 'render_to_response_view_with_content_type'),
- (r'^shortcuts/render/$', 'render_view'),
- (r'^shortcuts/render/base_context/$', 'render_view_with_base_context'),
- (r'^shortcuts/render/content_type/$', 'render_view_with_content_type'),
- (r'^shortcuts/render/status/$', 'render_view_with_status'),
- (r'^shortcuts/render/current_app/$', 'render_view_with_current_app'),
- (r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
-)
diff --git a/tests/views/locale/de/LC_MESSAGES/djangojs.mo b/tests/views/locale/de/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 34ba691029..0000000000
Binary files a/tests/views/locale/de/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/locale/de/LC_MESSAGES/djangojs.po b/tests/views/locale/de/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 88cc35e88f..0000000000
--- a/tests/views/locale/de/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,40 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django tests\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-02-14 17:33+0100\n"
-"PO-Revision-Date: 2011-01-21 21:37-0300\n"
-"Last-Translator: Jannis Leidel \n"
-"Language-Team: de \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-
-#: models.py:7
-msgctxt "month name"
-msgid "May"
-msgstr "Mai"
-
-#: models.py:9
-msgctxt "verb"
-msgid "May"
-msgstr "Kann"
-
-#: models.py:11
-msgid "%s item"
-msgid_plural "%s items"
-msgstr[0] "%s Element"
-msgstr[1] "%s Elemente"
-
-#: models.py:11
-msgctxt "search"
-msgid "%s result"
-msgid_plural "%s results"
-msgstr[0] "%s Resultat"
-msgstr[1] "%s Resultate"
diff --git a/tests/views/locale/es/LC_MESSAGES/djangojs.mo b/tests/views/locale/es/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index b6b088752a..0000000000
Binary files a/tests/views/locale/es/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/locale/es/LC_MESSAGES/djangojs.po b/tests/views/locale/es/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 669af4bf19..0000000000
--- a/tests/views/locale/es/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,25 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 16:45+0200\n"
-"PO-Revision-Date: 2010-05-12 12:57-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: media/js/translate.js:1
-msgid "this is to be translated"
-msgstr "esto tiene que ser traducido"
-
-
-msgid "Choose a time"
-msgstr "Elige una hora"
diff --git a/tests/views/locale/fr/LC_MESSAGES/djangojs.mo b/tests/views/locale/fr/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 537135ec95..0000000000
Binary files a/tests/views/locale/fr/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/locale/fr/LC_MESSAGES/djangojs.po b/tests/views/locale/fr/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 9259aab91b..0000000000
--- a/tests/views/locale/fr/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,28 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 19:15+0200\n"
-"PO-Revision-Date: 2010-05-12 12:41-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "this is to be translated"
-msgstr "il faut le traduire"
-
-
-msgid "Choose a time"
-msgstr "Choisir une heure"
-
-msgctxt "month name"
-msgid "May"
-msgstr "mai"
diff --git a/tests/views/locale/ru/LC_MESSAGES/djangojs.mo b/tests/views/locale/ru/LC_MESSAGES/djangojs.mo
deleted file mode 100644
index 21659a93d3..0000000000
Binary files a/tests/views/locale/ru/LC_MESSAGES/djangojs.mo and /dev/null differ
diff --git a/tests/views/locale/ru/LC_MESSAGES/djangojs.po b/tests/views/locale/ru/LC_MESSAGES/djangojs.po
deleted file mode 100644
index 4ea193a880..0000000000
--- a/tests/views/locale/ru/LC_MESSAGES/djangojs.po
+++ /dev/null
@@ -1,24 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-15 16:45+0200\n"
-"PO-Revision-Date: 2010-05-12 12:57-0300\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-msgid "this is to be translated"
-msgstr "перевод"
-
-
-msgid "Choose a time"
-msgstr "Выберите время"
diff --git a/tests/views/media/file.txt b/tests/views/media/file.txt
deleted file mode 100644
index f1fc82c455..0000000000
--- a/tests/views/media/file.txt
+++ /dev/null
@@ -1 +0,0 @@
-An example media file.
\ No newline at end of file
diff --git a/tests/views/media/file.txt.gz b/tests/views/media/file.txt.gz
deleted file mode 100644
index 0ee7d18311..0000000000
Binary files a/tests/views/media/file.txt.gz and /dev/null differ
diff --git a/tests/views/media/file.unknown b/tests/views/media/file.unknown
deleted file mode 100644
index 77dcda8970..0000000000
--- a/tests/views/media/file.unknown
+++ /dev/null
@@ -1 +0,0 @@
-An unknown file extension.
diff --git a/tests/views/models.py b/tests/views/models.py
deleted file mode 100644
index 461f98c028..0000000000
--- a/tests/views/models.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-Regression tests for Django built-in views.
-"""
-
-from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
-
-@python_2_unicode_compatible
-class Author(models.Model):
- name = models.CharField(max_length=100)
-
- def __str__(self):
- return self.name
-
- def get_absolute_url(self):
- return '/views/authors/%s/' % self.id
-
-@python_2_unicode_compatible
-class BaseArticle(models.Model):
- """
- An abstract article Model so that we can create article models with and
- without a get_absolute_url method (for create_update generic views tests).
- """
- title = models.CharField(max_length=100)
- slug = models.SlugField()
- author = models.ForeignKey(Author)
-
- class Meta:
- abstract = True
-
- def __str__(self):
- return self.title
-
-class Article(BaseArticle):
- date_created = models.DateTimeField()
-
-class UrlArticle(BaseArticle):
- """
- An Article class with a get_absolute_url defined.
- """
- date_created = models.DateTimeField()
-
- def get_absolute_url(self):
- return '/urlarticles/%s/' % self.slug
- get_absolute_url.purge = True
-
-class DateArticle(BaseArticle):
- """
- An article Model with a DateField instead of DateTimeField,
- for testing #7602
- """
- date_created = models.DateField()
diff --git a/tests/views/templates/debug/render_test.html b/tests/views/templates/debug/render_test.html
deleted file mode 100644
index 1b2f47a0e3..0000000000
--- a/tests/views/templates/debug/render_test.html
+++ /dev/null
@@ -1 +0,0 @@
-{{ foo }}.{{ bar }}.{{ baz }}.{{ STATIC_URL }}
diff --git a/tests/views/templates/debug/template_exception.html b/tests/views/templates/debug/template_exception.html
deleted file mode 100644
index c6b34a8388..0000000000
--- a/tests/views/templates/debug/template_exception.html
+++ /dev/null
@@ -1,2 +0,0 @@
-{% load debugtags %}
-{% go_boom arg %}
diff --git a/tests/views/templates/jsi18n.html b/tests/views/templates/jsi18n.html
deleted file mode 100644
index d6093c8ef4..0000000000
--- a/tests/views/templates/jsi18n.html
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
', status_code=403)
- finally:
- restore_template_loaders()
-
- def test_403_template(self):
- # Set up a test 403.html template.
- setup_test_template_loader(
- {'403.html': 'This is a test template for a 403 Forbidden error.'}
- )
- try:
- response = self.client.get('/views/raises403/')
- self.assertContains(response, 'test template', status_code=403)
- finally:
- restore_template_loaders()
-
- def test_404(self):
- response = self.client.get('/views/raises404/')
- self.assertEqual(response.status_code, 404)
-
- def test_view_exceptions(self):
- for n in range(len(except_args)):
- self.assertRaises(BrokenException, self.client.get,
- reverse('view_exception', args=(n,)))
-
- def test_template_exceptions(self):
- for n in range(len(except_args)):
- try:
- self.client.get(reverse('template_exception', args=(n,)))
- except Exception:
- raising_loc = inspect.trace()[-1][-2][0].strip()
- self.assertFalse(raising_loc.find('raise BrokenException') == -1,
- "Failed to find 'raise BrokenException' in last frame of traceback, instead found: %s" %
- raising_loc)
-
- def test_template_loader_postmortem(self):
- response = self.client.get(reverse('raises_template_does_not_exist'))
- template_path = os.path.join('templates', 'i_dont_exist.html')
- self.assertContains(response, template_path, status_code=500)
-
-
-class ExceptionReporterTests(TestCase):
- rf = RequestFactory()
-
- def test_request_and_exception(self):
- "A simple exception report can be generated"
- try:
- request = self.rf.get('/test_view/')
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn('
ValueError at /test_view/
', html)
- self.assertIn('
Can't find my keys
', html)
- self.assertIn('
Request Method:
', html)
- self.assertIn('
Request URL:
', html)
- self.assertIn('
Exception Type:
', html)
- self.assertIn('
Exception Value:
', html)
- self.assertIn('
Traceback ', html)
- self.assertIn('
Request information
', html)
- self.assertNotIn('
Request data not supplied
', html)
-
- def test_no_request(self):
- "An exception report can be generated without request"
- try:
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn('
ValueError
', html)
- self.assertIn('
Can't find my keys
', html)
- self.assertNotIn('
Request Method:
', html)
- self.assertNotIn('
Request URL:
', html)
- self.assertIn('
Exception Type:
', html)
- self.assertIn('
Exception Value:
', html)
- self.assertIn('
Traceback ', html)
- self.assertIn('
Request information
', html)
- self.assertIn('
Request data not supplied
', html)
-
- def test_no_exception(self):
- "An exception report can be generated for just a request"
- request = self.rf.get('/test_view/')
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertIn('
Report at /test_view/
', html)
- self.assertIn('
No exception supplied
', html)
- self.assertIn('
Request Method:
', html)
- self.assertIn('
Request URL:
', html)
- self.assertNotIn('
Exception Type:
', html)
- self.assertNotIn('
Exception Value:
', html)
- self.assertNotIn('
Traceback ', html)
- self.assertIn('
Request information
', html)
- self.assertNotIn('
Request data not supplied
', html)
-
- def test_request_and_message(self):
- "A message can be provided in addition to a request"
- request = self.rf.get('/test_view/')
- reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
- html = reporter.get_traceback_html()
- self.assertIn('
Report at /test_view/
', html)
- self.assertIn('
I'm a little teapot
', html)
- self.assertIn('
Request Method:
', html)
- self.assertIn('
Request URL:
', html)
- self.assertNotIn('
Exception Type:
', html)
- self.assertNotIn('
Exception Value:
', html)
- self.assertNotIn('
Traceback ', html)
- self.assertIn('
Request information
', html)
- self.assertNotIn('
Request data not supplied
', html)
-
- def test_message_only(self):
- reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
- html = reporter.get_traceback_html()
- self.assertIn('
Report
', html)
- self.assertIn('
I'm a little teapot
', html)
- self.assertNotIn('
Request Method:
', html)
- self.assertNotIn('
Request URL:
', html)
- self.assertNotIn('
Exception Type:
', html)
- self.assertNotIn('
Exception Value:
', html)
- self.assertNotIn('
Traceback ', html)
- self.assertIn('
Request information
', html)
- self.assertIn('
Request data not supplied
', html)
-
-
-class PlainTextReportTests(TestCase):
- rf = RequestFactory()
-
- def test_request_and_exception(self):
- "A simple exception report can be generated"
- try:
- request = self.rf.get('/test_view/')
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- text = reporter.get_traceback_text()
- self.assertIn('ValueError at /test_view/', text)
- self.assertIn("Can't find my keys", text)
- self.assertIn('Request Method:', text)
- self.assertIn('Request URL:', text)
- self.assertIn('Exception Type:', text)
- self.assertIn('Exception Value:', text)
- self.assertIn('Traceback:', text)
- self.assertIn('Request information:', text)
- self.assertNotIn('Request data not supplied', text)
-
- def test_no_request(self):
- "An exception report can be generated without request"
- try:
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- text = reporter.get_traceback_text()
- self.assertIn('ValueError', text)
- self.assertIn("Can't find my keys", text)
- self.assertNotIn('Request Method:', text)
- self.assertNotIn('Request URL:', text)
- self.assertIn('Exception Type:', text)
- self.assertIn('Exception Value:', text)
- self.assertIn('Traceback:', text)
- self.assertIn('Request data not supplied', text)
-
- def test_no_exception(self):
- "An exception report can be generated for just a request"
- request = self.rf.get('/test_view/')
- reporter = ExceptionReporter(request, None, None, None)
- text = reporter.get_traceback_text()
-
- def test_request_and_message(self):
- "A message can be provided in addition to a request"
- request = self.rf.get('/test_view/')
- reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
- text = reporter.get_traceback_text()
-
- def test_message_only(self):
- reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
- text = reporter.get_traceback_text()
-
-
-class ExceptionReportTestMixin(object):
-
- # Mixin used in the ExceptionReporterFilterTests and
- # AjaxResponseExceptionReporterFilter tests below
-
- breakfast_data = {'sausage-key': 'sausage-value',
- 'baked-beans-key': 'baked-beans-value',
- 'hash-brown-key': 'hash-brown-value',
- 'bacon-key': 'bacon-value',}
-
- def verify_unsafe_response(self, view, check_for_vars=True,
- check_for_POST_params=True):
- """
- Asserts that potentially sensitive info are displayed in the response.
- """
- request = self.rf.post('/some_url/', self.breakfast_data)
- response = view(request)
- if check_for_vars:
- # All variables are shown.
- self.assertContains(response, 'cooked_eggs', status_code=500)
- self.assertContains(response, 'scrambled', status_code=500)
- self.assertContains(response, 'sauce', status_code=500)
- self.assertContains(response, 'worcestershire', status_code=500)
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
- # All POST parameters are shown.
- self.assertContains(response, k, status_code=500)
- self.assertContains(response, v, status_code=500)
-
- def verify_safe_response(self, view, check_for_vars=True,
- check_for_POST_params=True):
- """
- Asserts that certain sensitive info are not displayed in the response.
- """
- request = self.rf.post('/some_url/', self.breakfast_data)
- response = view(request)
- if check_for_vars:
- # Non-sensitive variable's name and value are shown.
- self.assertContains(response, 'cooked_eggs', status_code=500)
- self.assertContains(response, 'scrambled', status_code=500)
- # Sensitive variable's name is shown but not its value.
- self.assertContains(response, 'sauce', status_code=500)
- self.assertNotContains(response, 'worcestershire', status_code=500)
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
- # All POST parameters' names are shown.
- self.assertContains(response, k, status_code=500)
- # Non-sensitive POST parameters' values are shown.
- self.assertContains(response, 'baked-beans-value', status_code=500)
- self.assertContains(response, 'hash-brown-value', status_code=500)
- # Sensitive POST parameters' values are not shown.
- self.assertNotContains(response, 'sausage-value', status_code=500)
- self.assertNotContains(response, 'bacon-value', status_code=500)
-
- def verify_paranoid_response(self, view, check_for_vars=True,
- check_for_POST_params=True):
- """
- Asserts that no variables or POST parameters are displayed in the response.
- """
- request = self.rf.post('/some_url/', self.breakfast_data)
- response = view(request)
- if check_for_vars:
- # Show variable names but not their values.
- self.assertContains(response, 'cooked_eggs', status_code=500)
- self.assertNotContains(response, 'scrambled', status_code=500)
- self.assertContains(response, 'sauce', status_code=500)
- self.assertNotContains(response, 'worcestershire', status_code=500)
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
- # All POST parameters' names are shown.
- self.assertContains(response, k, status_code=500)
- # No POST parameters' values are shown.
- self.assertNotContains(response, v, status_code=500)
-
- def verify_unsafe_email(self, view, check_for_POST_params=True):
- """
- Asserts that potentially sensitive info are displayed in the email report.
- """
- with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
- mail.outbox = [] # Empty outbox
- request = self.rf.post('/some_url/', self.breakfast_data)
- response = view(request)
- self.assertEqual(len(mail.outbox), 1)
- email = mail.outbox[0]
-
- # Frames vars are never shown in plain text email reports.
- body_plain = force_text(email.body)
- self.assertNotIn('cooked_eggs', body_plain)
- self.assertNotIn('scrambled', body_plain)
- self.assertNotIn('sauce', body_plain)
- self.assertNotIn('worcestershire', body_plain)
-
- # Frames vars are shown in html email reports.
- body_html = force_text(email.alternatives[0][0])
- self.assertIn('cooked_eggs', body_html)
- self.assertIn('scrambled', body_html)
- self.assertIn('sauce', body_html)
- self.assertIn('worcestershire', body_html)
-
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
- # All POST parameters are shown.
- self.assertIn(k, body_plain)
- self.assertIn(v, body_plain)
- self.assertIn(k, body_html)
- self.assertIn(v, body_html)
-
- def verify_safe_email(self, view, check_for_POST_params=True):
- """
- Asserts that certain sensitive info are not displayed in the email report.
- """
- with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
- mail.outbox = [] # Empty outbox
- request = self.rf.post('/some_url/', self.breakfast_data)
- response = view(request)
- self.assertEqual(len(mail.outbox), 1)
- email = mail.outbox[0]
-
- # Frames vars are never shown in plain text email reports.
- body_plain = force_text(email.body)
- self.assertNotIn('cooked_eggs', body_plain)
- self.assertNotIn('scrambled', body_plain)
- self.assertNotIn('sauce', body_plain)
- self.assertNotIn('worcestershire', body_plain)
-
- # Frames vars are shown in html email reports.
- body_html = force_text(email.alternatives[0][0])
- self.assertIn('cooked_eggs', body_html)
- self.assertIn('scrambled', body_html)
- self.assertIn('sauce', body_html)
- self.assertNotIn('worcestershire', body_html)
-
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
- # All POST parameters' names are shown.
- self.assertIn(k, body_plain)
- # Non-sensitive POST parameters' values are shown.
- self.assertIn('baked-beans-value', body_plain)
- self.assertIn('hash-brown-value', body_plain)
- self.assertIn('baked-beans-value', body_html)
- self.assertIn('hash-brown-value', body_html)
- # Sensitive POST parameters' values are not shown.
- self.assertNotIn('sausage-value', body_plain)
- self.assertNotIn('bacon-value', body_plain)
- self.assertNotIn('sausage-value', body_html)
- self.assertNotIn('bacon-value', body_html)
-
- def verify_paranoid_email(self, view):
- """
- Asserts that no variables or POST parameters are displayed in the email report.
- """
- with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
- mail.outbox = [] # Empty outbox
- request = self.rf.post('/some_url/', self.breakfast_data)
- response = view(request)
- self.assertEqual(len(mail.outbox), 1)
- email = mail.outbox[0]
- # Frames vars are never shown in plain text email reports.
- body = force_text(email.body)
- self.assertNotIn('cooked_eggs', body)
- self.assertNotIn('scrambled', body)
- self.assertNotIn('sauce', body)
- self.assertNotIn('worcestershire', body)
- for k, v in self.breakfast_data.items():
- # All POST parameters' names are shown.
- self.assertIn(k, body)
- # No POST parameters' values are shown.
- self.assertNotIn(v, body)
-
-
-class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin):
- """
- Ensure that sensitive information can be filtered out of error reports.
- Refs #14614.
- """
- rf = RequestFactory()
-
- def test_non_sensitive_request(self):
- """
- Ensure that everything (request info and frame variables) can bee seen
- in the default error reports for non-sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(non_sensitive_view)
- self.verify_unsafe_email(non_sensitive_view)
-
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(non_sensitive_view)
- self.verify_unsafe_email(non_sensitive_view)
-
- def test_sensitive_request(self):
- """
- Ensure that sensitive POST parameters and frame variables cannot be
- seen in the default error reports for sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_view)
- self.verify_unsafe_email(sensitive_view)
-
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_view)
- self.verify_safe_email(sensitive_view)
-
- def test_paranoid_request(self):
- """
- Ensure that no POST parameters and frame variables can be seen in the
- default error reports for "paranoid" requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(paranoid_view)
- self.verify_unsafe_email(paranoid_view)
-
- with self.settings(DEBUG=False):
- self.verify_paranoid_response(paranoid_view)
- self.verify_paranoid_email(paranoid_view)
-
- def test_custom_exception_reporter_filter(self):
- """
- Ensure that it's possible to assign an exception reporter filter to
- the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(custom_exception_reporter_filter_view)
- self.verify_unsafe_email(custom_exception_reporter_filter_view)
-
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(custom_exception_reporter_filter_view)
- self.verify_unsafe_email(custom_exception_reporter_filter_view)
-
- def test_sensitive_method(self):
- """
- Ensure that the sensitive_variables decorator works with object
- methods.
- Refs #18379.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_method_view,
- check_for_POST_params=False)
- self.verify_unsafe_email(sensitive_method_view,
- check_for_POST_params=False)
-
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_method_view,
- check_for_POST_params=False)
- self.verify_safe_email(sensitive_method_view,
- check_for_POST_params=False)
-
- def test_sensitive_function_arguments(self):
- """
- Ensure that sensitive variables don't leak in the sensitive_variables
- decorator's frame, when those variables are passed as arguments to the
- decorated function.
- Refs #19453.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_args_function_caller)
- self.verify_unsafe_email(sensitive_args_function_caller)
-
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_args_function_caller, check_for_POST_params=False)
- self.verify_safe_email(sensitive_args_function_caller, check_for_POST_params=False)
-
- def test_sensitive_function_keyword_arguments(self):
- """
- Ensure that sensitive variables don't leak in the sensitive_variables
- decorator's frame, when those variables are passed as keyword arguments
- to the decorated function.
- Refs #19453.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_kwargs_function_caller)
- self.verify_unsafe_email(sensitive_kwargs_function_caller)
-
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_kwargs_function_caller, check_for_POST_params=False)
- self.verify_safe_email(sensitive_kwargs_function_caller, check_for_POST_params=False)
-
-
-class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin):
- """
- Ensure that sensitive information can be filtered out of error reports.
-
- Here we specifically test the plain text 500 debug-only error page served
- when it has been detected the request was sent by JS code. We don't check
- for (non)existence of frames vars in the traceback information section of
- the response content because we don't include them in these error pages.
- Refs #14614.
- """
- rf = RequestFactory(HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-
- def test_non_sensitive_request(self):
- """
- Ensure that request info can bee seen in the default error reports for
- non-sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
-
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
-
- def test_sensitive_request(self):
- """
- Ensure that sensitive POST parameters cannot be seen in the default
- error reports for sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_view, check_for_vars=False)
-
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_view, check_for_vars=False)
-
- def test_paranoid_request(self):
- """
- Ensure that no POST parameters can be seen in the default error reports
- for "paranoid" requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(paranoid_view, check_for_vars=False)
-
- with self.settings(DEBUG=False):
- self.verify_paranoid_response(paranoid_view, check_for_vars=False)
-
- def test_custom_exception_reporter_filter(self):
- """
- Ensure that it's possible to assign an exception reporter filter to
- the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(custom_exception_reporter_filter_view,
- check_for_vars=False)
-
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(custom_exception_reporter_filter_view,
- check_for_vars=False)
diff --git a/tests/views/tests/defaults.py b/tests/views/tests/defaults.py
deleted file mode 100644
index 3ca7f79136..0000000000
--- a/tests/views/tests/defaults.py
+++ /dev/null
@@ -1,98 +0,0 @@
-from __future__ import absolute_import, unicode_literals
-
-from django.contrib.contenttypes.models import ContentType
-from django.test import TestCase
-from django.test.utils import setup_test_template_loader, restore_template_loaders
-
-from ..models import Author, Article, UrlArticle
-
-
-class DefaultsTests(TestCase):
- """Test django views in django/views/defaults.py"""
- fixtures = ['testdata.json']
- non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
- '/views/other_non_existing_url/'] # this NOT in urls.py
-
- def test_shortcut_with_absolute_url(self):
- "Can view a shortcut for an Author object that has a get_absolute_url method"
- for obj in Author.objects.all():
- short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk)
- response = self.client.get(short_url)
- self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(),
- status_code=302, target_status_code=404)
-
- def test_shortcut_no_absolute_url(self):
- "Shortcuts for an object that has no get_absolute_url method raises 404"
- for obj in Article.objects.all():
- short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk)
- response = self.client.get(short_url)
- self.assertEqual(response.status_code, 404)
-
- def test_wrong_type_pk(self):
- short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects')
- response = self.client.get(short_url)
- self.assertEqual(response.status_code, 404)
-
- def test_shortcut_bad_pk(self):
- short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242')
- response = self.client.get(short_url)
- self.assertEqual(response.status_code, 404)
-
- def test_nonint_content_type(self):
- an_author = Author.objects.all()[0]
- short_url = '/views/shortcut/%s/%s/' % ('spam', an_author.pk)
- response = self.client.get(short_url)
- self.assertEqual(response.status_code, 404)
-
- def test_bad_content_type(self):
- an_author = Author.objects.all()[0]
- short_url = '/views/shortcut/%s/%s/' % (42424242, an_author.pk)
- response = self.client.get(short_url)
- self.assertEqual(response.status_code, 404)
-
- def test_page_not_found(self):
- "A 404 status is returned by the page_not_found view"
- for url in self.non_existing_urls:
- response = self.client.get(url)
- self.assertEqual(response.status_code, 404)
-
- def test_csrf_token_in_404(self):
- """
- The 404 page should have the csrf_token available in the context
- """
- # See ticket #14565
- for url in self.non_existing_urls:
- response = self.client.get(url)
- csrf_token = response.context['csrf_token']
- self.assertNotEqual(str(csrf_token), 'NOTPROVIDED')
- self.assertNotEqual(str(csrf_token), '')
-
- def test_server_error(self):
- "The server_error view raises a 500 status"
- response = self.client.get('/views/server_error/')
- self.assertEqual(response.status_code, 500)
-
- def test_custom_templates(self):
- """
- Test that 404.html and 500.html templates are picked by their respective
- handler.
- """
- setup_test_template_loader(
- {'404.html': 'This is a test template for a 404 error.',
- '500.html': 'This is a test template for a 500 error.'}
- )
- try:
- for code, url in ((404, '/views/non_existing_url/'), (500, '/views/server_error/')):
- response = self.client.get(url)
- self.assertContains(response, "test template for a %d error" % code,
- status_code=code)
- finally:
- restore_template_loaders()
-
- def test_get_absolute_url_attributes(self):
- "A model can set attributes on the get_absolute_url method"
- self.assertTrue(getattr(UrlArticle.get_absolute_url, 'purge', False),
- 'The attributes of the original get_absolute_url must be added.')
- article = UrlArticle.objects.get(pk=1)
- self.assertTrue(getattr(article.get_absolute_url, 'purge', False),
- 'The attributes of the original get_absolute_url must be added.')
diff --git a/tests/views/tests/i18n.py b/tests/views/tests/i18n.py
deleted file mode 100644
index 33ffab59f2..0000000000
--- a/tests/views/tests/i18n.py
+++ /dev/null
@@ -1,221 +0,0 @@
-# -*- coding:utf-8 -*-
-from __future__ import absolute_import
-
-import gettext
-import os
-from os import path
-
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django.test import LiveServerTestCase, TestCase
-from django.test.utils import override_settings
-from django.utils import six, unittest
-from django.utils._os import upath
-from django.utils.translation import override
-from django.utils.text import javascript_quote
-
-try:
- from selenium.webdriver.firefox import webdriver as firefox
-except ImportError:
- firefox = None
-
-from ..urls import locale_dir
-
-
-class I18NTests(TestCase):
- """ Tests django views in django/views/i18n.py """
-
- def test_setlang(self):
- """
- The set_language view can be used to change the session language.
-
- The user is redirected to the 'next' argument if provided.
- """
- for lang_code, lang_name in settings.LANGUAGES:
- post_data = dict(language=lang_code, next='/views/')
- response = self.client.post('/views/i18n/setlang/', data=post_data)
- self.assertRedirects(response, 'http://testserver/views/')
- self.assertEqual(self.client.session['django_language'], lang_code)
-
- def test_setlang_unsafe_next(self):
- """
- The set_language view only redirects to the 'next' argument if it is
- "safe".
- """
- lang_code, lang_name = settings.LANGUAGES[0]
- post_data = dict(language=lang_code, next='//unsafe/redirection/')
- response = self.client.post('/views/i18n/setlang/', data=post_data)
- self.assertEqual(response.url, 'http://testserver/')
- self.assertEqual(self.client.session['django_language'], lang_code)
-
- def test_setlang_reversal(self):
- self.assertEqual(reverse('set_language'), '/views/i18n/setlang/')
-
- def test_jsi18n(self):
- """The javascript_catalog can be deployed with language settings"""
- for lang_code in ['es', 'fr', 'ru']:
- with override(lang_code):
- catalog = gettext.translation('djangojs', locale_dir, [lang_code])
- if six.PY3:
- trans_txt = catalog.gettext('this is to be translated')
- else:
- trans_txt = catalog.ugettext('this is to be translated')
- response = self.client.get('/views/jsi18n/')
- # in response content must to be a line like that:
- # catalog['this is to be translated'] = 'same_that_trans_txt'
- # javascript_quote is used to be able to check unicode strings
- self.assertContains(response, javascript_quote(trans_txt), 1)
- if lang_code == 'fr':
- # Message with context (msgctxt)
- self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
-
-
-class JsI18NTests(TestCase):
- """
- Tests django views in django/views/i18n.py that need to change
- settings.LANGUAGE_CODE.
- """
-
- def test_jsi18n_with_missing_en_files(self):
- """
- The javascript_catalog shouldn't load the fallback language in the
- case that the current selected language is actually the one translated
- from, and hence missing translation files completely.
-
- This happens easily when you're translating from English to other
- languages and you've set settings.LANGUAGE_CODE to some other language
- than English.
- """
- with self.settings(LANGUAGE_CODE='es'):
- with override('en-us'):
- response = self.client.get('/views/jsi18n/')
- self.assertNotContains(response, 'esto tiene que ser traducido')
-
- def test_jsi18n_fallback_language(self):
- """
- Let's make sure that the fallback language is still working properly
- in cases where the selected language cannot be found.
- """
- with self.settings(LANGUAGE_CODE='fr'):
- with override('fi'):
- response = self.client.get('/views/jsi18n/')
- self.assertContains(response, 'il faut le traduire')
-
- def testI18NLanguageNonEnglishDefault(self):
- """
- Check if the Javascript i18n view returns an empty language catalog
- if the default language is non-English, the selected language
- is English and there is not 'en' translation available. See #13388,
- #3594 and #13726 for more details.
- """
- with self.settings(LANGUAGE_CODE='fr'):
- with override('en-us'):
- response = self.client.get('/views/jsi18n/')
- self.assertNotContains(response, 'Choisir une heure')
-
- def test_nonenglish_default_english_userpref(self):
- """
- Same as above with the difference that there IS an 'en' translation
- available. The Javascript i18n view must return a NON empty language catalog
- with the proper English translations. See #13726 for more details.
- """
- extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views.app0']
- with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps):
- with override('en-us'):
- response = self.client.get('/views/jsi18n_english_translation/')
- self.assertContains(response, javascript_quote('this app0 string is to be translated'))
-
- def testI18NLanguageNonEnglishFallback(self):
- """
- Makes sure that the fallback language is still working properly
- in cases where the selected language cannot be found.
- """
- with self.settings(LANGUAGE_CODE='fr'):
- with override('none'):
- response = self.client.get('/views/jsi18n/')
- self.assertContains(response, 'Choisir une heure')
-
-
-class JsI18NTestsMultiPackage(TestCase):
- """
- Tests for django views in django/views/i18n.py that need to change
- settings.LANGUAGE_CODE and merge JS translation from several packages.
- """
- def testI18NLanguageEnglishDefault(self):
- """
- Check if the JavaScript i18n view returns a complete language catalog
- if the default language is en-us, the selected language has a
- translation available and a catalog composed by djangojs domain
- translations of multiple Python packages is requested. See #13388,
- #3594 and #13514 for more details.
- """
- extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views.app1', 'regressiontests.views.app2']
- with self.settings(LANGUAGE_CODE='en-us', INSTALLED_APPS=extended_apps):
- with override('fr'):
- response = self.client.get('/views/jsi18n_multi_packages1/')
- self.assertContains(response, javascript_quote('il faut traduire cette chaîne de caractères de app1'))
-
- def testI18NDifferentNonEnLangs(self):
- """
- Similar to above but with neither default or requested language being
- English.
- """
- extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views.app3', 'regressiontests.views.app4']
- with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps):
- with override('es-ar'):
- response = self.client.get('/views/jsi18n_multi_packages2/')
- self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))
-
- def testI18NWithLocalePaths(self):
- extended_locale_paths = settings.LOCALE_PATHS + (
- path.join(path.dirname(
- path.dirname(path.abspath(upath(__file__)))), 'app3', 'locale'),)
- with self.settings(LANGUAGE_CODE='es-ar', LOCALE_PATHS=extended_locale_paths):
- with override('es-ar'):
- response = self.client.get('/views/jsi18n/')
- self.assertContains(response,
- javascript_quote('este texto de app3 debe ser traducido'))
-
-
-skip_selenium = not os.environ.get('DJANGO_SELENIUM_TESTS', False)
-
-
-@unittest.skipIf(skip_selenium, 'Selenium tests not requested')
-@unittest.skipUnless(firefox, 'Selenium not installed')
-class JavascriptI18nTests(LiveServerTestCase):
- urls = 'regressiontests.views.urls'
-
- @classmethod
- def setUpClass(cls):
- cls.selenium = firefox.WebDriver()
- super(JavascriptI18nTests, cls).setUpClass()
-
- @classmethod
- def tearDownClass(cls):
- cls.selenium.quit()
- super(JavascriptI18nTests, cls).tearDownClass()
-
- @override_settings(LANGUAGE_CODE='de')
- def test_javascript_gettext(self):
- extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views']
- with self.settings(INSTALLED_APPS=extended_apps):
- self.selenium.get('%s%s' % (self.live_server_url, '/jsi18n_template/'))
-
- elem = self.selenium.find_element_by_id("gettext")
- self.assertEqual(elem.text, "Entfernen")
- elem = self.selenium.find_element_by_id("ngettext_sing")
- self.assertEqual(elem.text, "1 Element")
- elem = self.selenium.find_element_by_id("ngettext_plur")
- self.assertEqual(elem.text, "455 Elemente")
- elem = self.selenium.find_element_by_id("pgettext")
- self.assertEqual(elem.text, "Kann")
- elem = self.selenium.find_element_by_id("npgettext_sing")
- self.assertEqual(elem.text, "1 Resultat")
- elem = self.selenium.find_element_by_id("npgettext_plur")
- self.assertEqual(elem.text, "455 Resultate")
-
- def test_escaping(self):
- extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views']
- with self.settings(INSTALLED_APPS=extended_apps):
- response = self.client.get('%s%s' % (self.live_server_url, '/jsi18n_admin/'))
- self.assertContains(response, '\\x04')
diff --git a/tests/views/tests/shortcuts.py b/tests/views/tests/shortcuts.py
deleted file mode 100644
index 3a5df6a9cb..0000000000
--- a/tests/views/tests/shortcuts.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from django.conf import settings
-from django.test import TestCase
-from django.test.utils import override_settings
-
-@override_settings(
- TEMPLATE_CONTEXT_PROCESSORS=('django.core.context_processors.static',),
- STATIC_URL='/path/to/static/media/',
-)
-class ShortcutTests(TestCase):
- urls = 'regressiontests.views.generic_urls'
-
- def test_render_to_response(self):
- response = self.client.get('/shortcuts/render_to_response/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'FOO.BAR..\n')
- self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
-
- def test_render_to_response_with_request_context(self):
- response = self.client.get('/shortcuts/render_to_response/request_context/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
- self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
-
- def test_render_to_response_with_content_type(self):
- response = self.client.get('/shortcuts/render_to_response/content_type/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'FOO.BAR..\n')
- self.assertEqual(response['Content-Type'], 'application/x-rendertest')
-
- def test_render(self):
- response = self.client.get('/shortcuts/render/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
- self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
- self.assertEqual(response.context.current_app, None)
-
- def test_render_with_base_context(self):
- response = self.client.get('/shortcuts/render/base_context/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'FOO.BAR..\n')
- self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
-
- def test_render_with_content_type(self):
- response = self.client.get('/shortcuts/render/content_type/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
- self.assertEqual(response['Content-Type'], 'application/x-rendertest')
-
- def test_render_with_status(self):
- response = self.client.get('/shortcuts/render/status/')
- self.assertEqual(response.status_code, 403)
- self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
-
- def test_render_with_current_app(self):
- response = self.client.get('/shortcuts/render/current_app/')
- self.assertEqual(response.context.current_app, "foobar_app")
-
- def test_render_with_current_app_conflict(self):
- self.assertRaises(ValueError, self.client.get, '/shortcuts/render/current_app_conflict/')
diff --git a/tests/views/tests/specials.py b/tests/views/tests/specials.py
deleted file mode 100644
index 7d652841f8..0000000000
--- a/tests/views/tests/specials.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# coding: utf-8
-from __future__ import unicode_literals
-
-from django.test import TestCase
-
-
-class URLHandling(TestCase):
- """
- Tests for URL handling in views and responses.
- """
- urls = 'regressiontests.views.generic_urls'
- redirect_target = "/%E4%B8%AD%E6%96%87/target/"
-
- def test_combining_redirect(self):
- """
- Tests that redirecting to an IRI, requiring encoding before we use it
- in an HTTP response, is handled correctly. In this case the arg to
- HttpRedirect is ASCII but the current request path contains non-ASCII
- characters so this test ensures the creation of the full path with a
- base non-ASCII part is handled correctly.
- """
- response = self.client.get('/中文/')
- self.assertRedirects(response, self.redirect_target)
-
- def test_nonascii_redirect(self):
- """
- Tests that a non-ASCII argument to HttpRedirect is handled properly.
- """
- response = self.client.get('/nonascii_redirect/')
- self.assertRedirects(response, self.redirect_target)
-
- def test_permanent_nonascii_redirect(self):
- """
- Tests that a non-ASCII argument to HttpPermanentRedirect is handled
- properly.
- """
- response = self.client.get('/permanent_nonascii_redirect/')
- self.assertRedirects(response, self.redirect_target, status_code=301)
-
diff --git a/tests/views/tests/static.py b/tests/views/tests/static.py
deleted file mode 100644
index bdd9fbfc0b..0000000000
--- a/tests/views/tests/static.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from __future__ import absolute_import
-
-import mimetypes
-from os import path
-import unittest
-
-from django.conf.urls.static import static
-from django.http import HttpResponseNotModified
-from django.test import TestCase
-from django.test.utils import override_settings
-from django.utils.http import http_date
-from django.views.static import was_modified_since
-
-from .. import urls
-from ..urls import media_dir
-
-
-@override_settings(DEBUG=True)
-class StaticTests(TestCase):
- """Tests django views in django/views/static.py"""
-
- prefix = 'site_media'
-
- def test_serve(self):
- "The static view can serve static media"
- media_files = ['file.txt', 'file.txt.gz']
- for filename in media_files:
- response = self.client.get('/views/%s/%s' % (self.prefix, filename))
- response_content = b''.join(response)
- file_path = path.join(media_dir, filename)
- with open(file_path, 'rb') as fp:
- self.assertEqual(fp.read(), response_content)
- self.assertEqual(len(response_content), int(response['Content-Length']))
- self.assertEqual(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))
-
- def test_unknown_mime_type(self):
- response = self.client.get('/views/%s/file.unknown' % self.prefix)
- self.assertEqual('application/octet-stream', response['Content-Type'])
-
- def test_copes_with_empty_path_component(self):
- file_name = 'file.txt'
- response = self.client.get('/views/%s//%s' % (self.prefix, file_name))
- response_content = b''.join(response)
- with open(path.join(media_dir, file_name), 'rb') as fp:
- self.assertEqual(fp.read(), response_content)
-
- def test_is_modified_since(self):
- file_name = 'file.txt'
- response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
- HTTP_IF_MODIFIED_SINCE='Thu, 1 Jan 1970 00:00:00 GMT')
- response_content = b''.join(response)
- with open(path.join(media_dir, file_name), 'rb') as fp:
- self.assertEqual(fp.read(), response_content)
-
- def test_not_modified_since(self):
- file_name = 'file.txt'
- response = self.client.get(
- '/views/%s/%s' % (self.prefix, file_name),
- HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 GMT'
- # This is 24h before max Unix time. Remember to fix Django and
- # update this test well before 2038 :)
- )
- self.assertTrue(isinstance(response, HttpResponseNotModified))
-
- def test_invalid_if_modified_since(self):
- """Handle bogus If-Modified-Since values gracefully
-
- Assume that a file is modified since an invalid timestamp as per RFC
- 2616, section 14.25.
- """
- file_name = 'file.txt'
- invalid_date = 'Mon, 28 May 999999999999 28:25:26 GMT'
- response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
- HTTP_IF_MODIFIED_SINCE=invalid_date)
- response_content = b''.join(response)
- with open(path.join(media_dir, file_name), 'rb') as fp:
- self.assertEqual(fp.read(), response_content)
- self.assertEqual(len(response_content),
- int(response['Content-Length']))
-
- def test_invalid_if_modified_since2(self):
- """Handle even more bogus If-Modified-Since values gracefully
-
- Assume that a file is modified since an invalid timestamp as per RFC
- 2616, section 14.25.
- """
- file_name = 'file.txt'
- invalid_date = ': 1291108438, Wed, 20 Oct 2010 14:05:00 GMT'
- response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
- HTTP_IF_MODIFIED_SINCE=invalid_date)
- response_content = b''.join(response)
- with open(path.join(media_dir, file_name), 'rb') as fp:
- self.assertEqual(fp.read(), response_content)
- self.assertEqual(len(response_content),
- int(response['Content-Length']))
-
-
-class StaticHelperTest(StaticTests):
- """
- Test case to make sure the static URL pattern helper works as expected
- """
- def setUp(self):
- super(StaticHelperTest, self).setUp()
- self._old_views_urlpatterns = urls.urlpatterns[:]
- urls.urlpatterns += static('/media/', document_root=media_dir)
-
- def tearDown(self):
- super(StaticHelperTest, self).tearDown()
- urls.urlpatterns = self._old_views_urlpatterns
-
-
-class StaticUtilsTests(unittest.TestCase):
- def test_was_modified_since_fp(self):
- """
- Test that a floating point mtime does not disturb was_modified_since.
- (#18675)
- """
- mtime = 1343416141.107817
- header = http_date(mtime)
- self.assertFalse(was_modified_since(header, mtime))
diff --git a/tests/views/urls.py b/tests/views/urls.py
deleted file mode 100644
index 2c06557ae9..0000000000
--- a/tests/views/urls.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-
-from os import path
-
-from django.conf.urls import patterns, url, include
-from django.utils._os import upath
-
-from . import views
-
-
-base_dir = path.dirname(path.abspath(upath(__file__)))
-media_dir = path.join(base_dir, 'media')
-locale_dir = path.join(base_dir, 'locale')
-
-js_info_dict = {
- 'domain': 'djangojs',
- 'packages': ('regressiontests.views',),
-}
-
-js_info_dict_english_translation = {
- 'domain': 'djangojs',
- 'packages': ('regressiontests.views.app0',),
-}
-
-js_info_dict_multi_packages1 = {
- 'domain': 'djangojs',
- 'packages': ('regressiontests.views.app1', 'regressiontests.views.app2'),
-}
-
-js_info_dict_multi_packages2 = {
- 'domain': 'djangojs',
- 'packages': ('regressiontests.views.app3', 'regressiontests.views.app4'),
-}
-
-js_info_dict_admin = {
- 'domain': 'djangojs',
- 'packages': ('django.contrib.admin', 'regressiontests.views'),
-}
-
-urlpatterns = patterns('',
- (r'^$', views.index_page),
-
- # Default views
- (r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
- (r'^non_existing_url/', 'django.views.defaults.page_not_found'),
- (r'^server_error/', 'django.views.defaults.server_error'),
-
- # a view that raises an exception for the debug view
- (r'raises/$', views.raises),
- (r'raises404/$', views.raises404),
- (r'raises403/$', views.raises403),
-
- # i18n views
- (r'^i18n/', include('django.conf.urls.i18n')),
- (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
- (r'^jsi18n_english_translation/$', 'django.views.i18n.javascript_catalog', js_info_dict_english_translation),
- (r'^jsi18n_multi_packages1/$', 'django.views.i18n.javascript_catalog', js_info_dict_multi_packages1),
- (r'^jsi18n_multi_packages2/$', 'django.views.i18n.javascript_catalog', js_info_dict_multi_packages2),
- (r'^jsi18n_admin/$', 'django.views.i18n.javascript_catalog', js_info_dict_admin),
- (r'^jsi18n_template/$', views.jsi18n),
-
- # Static views
- (r'^site_media/(?P.*)$', 'django.views.static.serve', {'document_root': media_dir}),
-)
-
-urlpatterns += patterns('regressiontests.views.views',
- url(r'view_exception/(?P\d+)/$', 'view_exception', name='view_exception'),
- url(r'template_exception/(?P\d+)/$', 'template_exception', name='template_exception'),
- url(r'^raises_template_does_not_exist/$', 'raises_template_does_not_exist', name='raises_template_does_not_exist'),
-)
diff --git a/tests/views/views.py b/tests/views/views.py
deleted file mode 100644
index 50ad98ac2d..0000000000
--- a/tests/views/views.py
+++ /dev/null
@@ -1,266 +0,0 @@
-from __future__ import absolute_import
-
-import sys
-
-from django.core.exceptions import PermissionDenied
-from django.core.urlresolvers import get_resolver
-from django.http import HttpResponse, HttpResponseRedirect
-from django.shortcuts import render_to_response, render
-from django.template import Context, RequestContext, TemplateDoesNotExist
-from django.views.debug import technical_500_response, SafeExceptionReporterFilter
-from django.views.decorators.debug import (sensitive_post_parameters,
- sensitive_variables)
-from django.utils.log import getLogger
-
-from . import BrokenException, except_args
-
-
-
-def index_page(request):
- """Dummy index page"""
- return HttpResponse('Dummy page')
-
-def raises(request):
- # Make sure that a callable that raises an exception in the stack frame's
- # local vars won't hijack the technical 500 response. See:
- # http://code.djangoproject.com/ticket/15025
- def callable():
- raise Exception
- try:
- raise Exception
- except Exception:
- return technical_500_response(request, *sys.exc_info())
-
-def raises404(request):
- resolver = get_resolver(None)
- resolver.resolve('')
-
-def raises403(request):
- raise PermissionDenied
-
-def redirect(request):
- """
- Forces an HTTP redirect.
- """
- return HttpResponseRedirect("target/")
-
-def view_exception(request, n):
- raise BrokenException(except_args[int(n)])
-
-def template_exception(request, n):
- return render_to_response('debug/template_exception.html',
- {'arg': except_args[int(n)]})
-
-def jsi18n(request):
- return render_to_response('jsi18n.html')
-
-# Some views to exercise the shortcuts
-
-def render_to_response_view(request):
- return render_to_response('debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- })
-
-def render_to_response_view_with_request_context(request):
- return render_to_response('debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, context_instance=RequestContext(request))
-
-def render_to_response_view_with_content_type(request):
- return render_to_response('debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, content_type='application/x-rendertest')
-
-def render_view(request):
- return render(request, 'debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- })
-
-def render_view_with_base_context(request):
- return render(request, 'debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, context_instance=Context())
-
-def render_view_with_content_type(request):
- return render(request, 'debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, content_type='application/x-rendertest')
-
-def render_view_with_status(request):
- return render(request, 'debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, status=403)
-
-def render_view_with_current_app(request):
- return render(request, 'debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, current_app="foobar_app")
-
-def render_view_with_current_app_conflict(request):
- # This should fail because we don't passing both a current_app and
- # context_instance:
- return render(request, 'debug/render_test.html', {
- 'foo': 'FOO',
- 'bar': 'BAR',
- }, current_app="foobar_app", context_instance=RequestContext(request))
-
-def raises_template_does_not_exist(request):
- # We need to inspect the HTML generated by the fancy 500 debug view but
- # the test client ignores it, so we send it explicitly.
- try:
- return render_to_response('i_dont_exist.html')
- except TemplateDoesNotExist:
- return technical_500_response(request, *sys.exc_info())
-
-def send_log(request, exc_info):
- logger = getLogger('django.request')
- # The default logging config has a logging filter to ensure admin emails are
- # only sent with DEBUG=False, but since someone might choose to remove that
- # filter, we still want to be able to test the behavior of error emails
- # with DEBUG=True. So we need to remove the filter temporarily.
- admin_email_handler = [
- h for h in logger.handlers
- if h.__class__.__name__ == "AdminEmailHandler"
- ][0]
- orig_filters = admin_email_handler.filters
- admin_email_handler.filters = []
- admin_email_handler.include_html = True
- logger.error('Internal Server Error: %s', request.path,
- exc_info=exc_info,
- extra={
- 'status_code': 500,
- 'request': request
- }
- )
- admin_email_handler.filters = orig_filters
-
-def non_sensitive_view(request):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's source
- # is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
- try:
- raise Exception
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-@sensitive_variables('sauce')
-@sensitive_post_parameters('bacon-key', 'sausage-key')
-def sensitive_view(request):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's source
- # is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
- try:
- raise Exception
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-@sensitive_variables()
-@sensitive_post_parameters()
-def paranoid_view(request):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's source
- # is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
- try:
- raise Exception
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-def sensitive_args_function_caller(request):
- try:
- sensitive_args_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']))
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-@sensitive_variables('sauce')
-def sensitive_args_function(sauce):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's source
- # is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- raise Exception
-
-def sensitive_kwargs_function_caller(request):
- try:
- sensitive_kwargs_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']))
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-@sensitive_variables('sauce')
-def sensitive_kwargs_function(sauce=None):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's source
- # is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- raise Exception
-
-class UnsafeExceptionReporterFilter(SafeExceptionReporterFilter):
- """
- Ignores all the filtering done by its parent class.
- """
-
- def get_post_parameters(self, request):
- return request.POST
-
- def get_traceback_frame_variables(self, request, tb_frame):
- return tb_frame.f_locals.items()
-
-
-@sensitive_variables()
-@sensitive_post_parameters()
-def custom_exception_reporter_filter_view(request):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's source
- # is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
- request.exception_reporter_filter = UnsafeExceptionReporterFilter()
- try:
- raise Exception
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-
-class Klass(object):
-
- @sensitive_variables('sauce')
- def method(self, request):
- # Do not just use plain strings for the variables' values in the code
- # so that the tests don't return false positives when the function's
- # source is displayed in the exception report.
- cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
- sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
- try:
- raise Exception
- except Exception:
- exc_info = sys.exc_info()
- send_log(request, exc_info)
- return technical_500_response(request, *exc_info)
-
-def sensitive_method_view(request):
- return Klass().method(request)
diff --git a/tests/wsgi/tests.py b/tests/wsgi/tests.py
index 0b8c9d2b67..9b7ee68afd 100644
--- a/tests/wsgi/tests.py
+++ b/tests/wsgi/tests.py
@@ -10,7 +10,7 @@ from django.utils import six, unittest
class WSGITest(TestCase):
- urls = "regressiontests.wsgi.urls"
+ urls = "wsgi.urls"
def test_get_wsgi_application(self):
"""
@@ -44,7 +44,7 @@ class WSGITest(TestCase):
class GetInternalWSGIApplicationTest(unittest.TestCase):
- @override_settings(WSGI_APPLICATION="regressiontests.wsgi.wsgi.application")
+ @override_settings(WSGI_APPLICATION="wsgi.wsgi.application")
def test_success(self):
"""
If ``WSGI_APPLICATION`` is a dotted path, the referenced object is
@@ -81,19 +81,19 @@ class GetInternalWSGIApplicationTest(unittest.TestCase):
basehttp.get_wsgi_application = _orig_get_wsgi_app
- @override_settings(WSGI_APPLICATION="regressiontests.wsgi.noexist.app")
+ @override_settings(WSGI_APPLICATION="wsgi.noexist.app")
def test_bad_module(self):
with six.assertRaisesRegex(self,
ImproperlyConfigured,
- r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; Error importing.*"):
+ r"^WSGI application 'wsgi.noexist.app' could not be loaded; Error importing.*"):
get_internal_wsgi_application()
- @override_settings(WSGI_APPLICATION="regressiontests.wsgi.wsgi.noexist")
+ @override_settings(WSGI_APPLICATION="wsgi.wsgi.noexist")
def test_bad_name(self):
with six.assertRaisesRegex(self,
ImproperlyConfigured,
- r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; Module.*"):
+ r"^WSGI application 'wsgi.wsgi.noexist' could not be loaded; Module.*"):
get_internal_wsgi_application()
--
cgit v1.3