summaryrefslogtreecommitdiff
path: root/parts/django/tests/regressiontests
diff options
context:
space:
mode:
Diffstat (limited to 'parts/django/tests/regressiontests')
-rw-r--r--parts/django/tests/regressiontests/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_changelist/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_changelist/models.py9
-rw-r--r--parts/django/tests/regressiontests/admin_changelist/tests.py103
-rw-r--r--parts/django/tests/regressiontests/admin_inlines/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml17
-rw-r--r--parts/django/tests/regressiontests/admin_inlines/models.py125
-rw-r--r--parts/django/tests/regressiontests/admin_inlines/tests.py121
-rw-r--r--parts/django/tests/regressiontests/admin_ordering/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_ordering/models.py10
-rw-r--r--parts/django/tests/regressiontests/admin_ordering/tests.py39
-rw-r--r--parts/django/tests/regressiontests/admin_registration/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_registration/models.py11
-rw-r--r--parts/django/tests/regressiontests/admin_registration/tests.py54
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/app_with_import/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/app_with_import/models.py7
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/broken_app/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/broken_app/models.py1
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/complex_app/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/complex_app/admin/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/complex_app/admin/foo.py3
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/complex_app/models/__init__.py4
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/complex_app/models/bar.py7
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/complex_app/models/foo.py6
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/management/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/management/commands/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/management/commands/app_command.py10
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/management/commands/base_command.py15
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/management/commands/label_command.py9
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/management/commands/noargs_command.py9
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/models.py12
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/simple_app/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/simple_app/models.py1
-rw-r--r--parts/django/tests/regressiontests/admin_scripts/tests.py1199
-rw-r--r--parts/django/tests/regressiontests/admin_util/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_util/models.py33
-rw-r--r--parts/django/tests/regressiontests/admin_util/tests.py239
-rw-r--r--parts/django/tests/regressiontests/admin_validation/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_validation/models.py47
-rw-r--r--parts/django/tests/regressiontests/admin_validation/tests.py247
-rw-r--r--parts/django/tests/regressiontests/admin_views/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_views/customadmin.py34
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml15
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/admin-views-colors.xml19
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml12
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/admin-views-person.xml18
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml63
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/admin-views-users.xml93
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/deleted-objects.xml53
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/multiple-child-classes.json107
-rw-r--r--parts/django/tests/regressiontests/admin_views/fixtures/string-primary-key.xml6
-rw-r--r--parts/django/tests/regressiontests/admin_views/models.py636
-rw-r--r--parts/django/tests/regressiontests/admin_views/tests.py2287
-rw-r--r--parts/django/tests/regressiontests/admin_views/urls.py11
-rw-r--r--parts/django/tests/regressiontests/admin_views/views.py6
-rw-r--r--parts/django/tests/regressiontests/admin_widgets/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml43
-rw-r--r--parts/django/tests/regressiontests/admin_widgets/models.py68
-rw-r--r--parts/django/tests/regressiontests/admin_widgets/tests.py316
-rw-r--r--parts/django/tests/regressiontests/admin_widgets/urls.py7
-rw-r--r--parts/django/tests/regressiontests/admin_widgets/widgetadmin.py30
-rw-r--r--parts/django/tests/regressiontests/aggregation_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/aggregation_regress/fixtures/initial_data.json257
-rw-r--r--parts/django/tests/regressiontests/aggregation_regress/models.py65
-rw-r--r--parts/django/tests/regressiontests/aggregation_regress/tests.py757
-rw-r--r--parts/django/tests/regressiontests/app_loading/__init__.py0
-rwxr-xr-xparts/django/tests/regressiontests/app_loading/eggs/brokenapp.eggbin0 -> 1605 bytes
-rwxr-xr-xparts/django/tests/regressiontests/app_loading/eggs/modelapp.eggbin0 -> 3347 bytes
-rwxr-xr-xparts/django/tests/regressiontests/app_loading/eggs/nomodelapp.eggbin0 -> 1211 bytes
-rwxr-xr-xparts/django/tests/regressiontests/app_loading/eggs/omelet.eggbin0 -> 9020 bytes
-rw-r--r--parts/django/tests/regressiontests/app_loading/models.py0
-rw-r--r--parts/django/tests/regressiontests/app_loading/parent/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/app_loading/parent/app/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/app_loading/parent/app1/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/app_loading/parent/app_2/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/app_loading/test_settings.py3
-rw-r--r--parts/django/tests/regressiontests/app_loading/tests.py83
-rw-r--r--parts/django/tests/regressiontests/backends/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/backends/models.py37
-rw-r--r--parts/django/tests/regressiontests/backends/tests.py208
-rw-r--r--parts/django/tests/regressiontests/bash_completion/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/bash_completion/management/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/bash_completion/management/commands/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/bash_completion/management/commands/test_command.py14
-rw-r--r--parts/django/tests/regressiontests/bash_completion/models.py1
-rw-r--r--parts/django/tests/regressiontests/bash_completion/tests.py87
-rw-r--r--parts/django/tests/regressiontests/bug639/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/bug639/models.py26
-rw-r--r--parts/django/tests/regressiontests/bug639/test.jpgbin0 -> 1780 bytes
-rw-r--r--parts/django/tests/regressiontests/bug639/tests.py41
-rw-r--r--parts/django/tests/regressiontests/bug8245/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/bug8245/admin.py7
-rw-r--r--parts/django/tests/regressiontests/bug8245/models.py4
-rw-r--r--parts/django/tests/regressiontests/bug8245/tests.py29
-rw-r--r--parts/django/tests/regressiontests/builtin_server/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/builtin_server/models.py0
-rw-r--r--parts/django/tests/regressiontests/builtin_server/tests.py51
-rw-r--r--parts/django/tests/regressiontests/cache/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/cache/liberal_backend.py9
-rw-r--r--parts/django/tests/regressiontests/cache/models.py11
-rw-r--r--parts/django/tests/regressiontests/cache/tests.py652
-rw-r--r--parts/django/tests/regressiontests/comment_tests/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/comment_tests/custom_comments/__init__.py32
-rw-r--r--parts/django/tests/regressiontests/comment_tests/custom_comments/forms.py4
-rw-r--r--parts/django/tests/regressiontests/comment_tests/custom_comments/models.py4
-rw-r--r--parts/django/tests/regressiontests/comment_tests/custom_comments/views.py13
-rw-r--r--parts/django/tests/regressiontests/comment_tests/fixtures/comment_tests.json53
-rw-r--r--parts/django/tests/regressiontests/comment_tests/fixtures/comment_utils.xml15
-rw-r--r--parts/django/tests/regressiontests/comment_tests/models.py34
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/__init__.py89
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/app_api_tests.py71
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/comment_form_tests.py84
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py75
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/comment_view_tests.py258
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/model_tests.py49
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/moderation_view_tests.py203
-rw-r--r--parts/django/tests/regressiontests/comment_tests/tests/templatetag_tests.py97
-rw-r--r--parts/django/tests/regressiontests/comment_tests/urls.py9
-rw-r--r--parts/django/tests/regressiontests/comment_tests/urls_admin.py19
-rw-r--r--parts/django/tests/regressiontests/conditional_processing/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/conditional_processing/models.py128
-rw-r--r--parts/django/tests/regressiontests/conditional_processing/urls.py10
-rw-r--r--parts/django/tests/regressiontests/conditional_processing/views.py26
-rw-r--r--parts/django/tests/regressiontests/context_processors/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/context_processors/fixtures/context-processors-users.xml17
-rw-r--r--parts/django/tests/regressiontests/context_processors/models.py1
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html1
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html1
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html1
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html1
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html1
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html4
-rw-r--r--parts/django/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html13
-rw-r--r--parts/django/tests/regressiontests/context_processors/tests.py112
-rw-r--r--parts/django/tests/regressiontests/context_processors/urls.py14
-rw-r--r--parts/django/tests/regressiontests/context_processors/views.py37
-rw-r--r--parts/django/tests/regressiontests/csrf_tests/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/csrf_tests/models.py1
-rw-r--r--parts/django/tests/regressiontests/csrf_tests/tests.py375
-rw-r--r--parts/django/tests/regressiontests/custom_columns_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/custom_columns_regress/models.py36
-rw-r--r--parts/django/tests/regressiontests/custom_columns_regress/tests.py84
-rw-r--r--parts/django/tests/regressiontests/custom_managers_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/custom_managers_regress/models.py40
-rw-r--r--parts/django/tests/regressiontests/custom_managers_regress/tests.py47
-rw-r--r--parts/django/tests/regressiontests/datatypes/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/datatypes/models.py25
-rw-r--r--parts/django/tests/regressiontests/datatypes/tests.py93
-rw-r--r--parts/django/tests/regressiontests/db_typecasts/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/db_typecasts/models.py0
-rw-r--r--parts/django/tests/regressiontests/db_typecasts/tests.py62
-rw-r--r--parts/django/tests/regressiontests/decorators/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/decorators/models.py2
-rw-r--r--parts/django/tests/regressiontests/decorators/tests.py141
-rw-r--r--parts/django/tests/regressiontests/defaultfilters/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/defaultfilters/models.py0
-rw-r--r--parts/django/tests/regressiontests/defaultfilters/tests.py485
-rw-r--r--parts/django/tests/regressiontests/defer_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/defer_regress/models.py36
-rw-r--r--parts/django/tests/regressiontests/defer_regress/tests.py163
-rw-r--r--parts/django/tests/regressiontests/delete_regress/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/delete_regress/models.py37
-rw-r--r--parts/django/tests/regressiontests/delete_regress/tests.py116
-rw-r--r--parts/django/tests/regressiontests/dispatch/__init__.py2
-rw-r--r--parts/django/tests/regressiontests/dispatch/models.py0
-rw-r--r--parts/django/tests/regressiontests/dispatch/tests/__init__.py6
-rw-r--r--parts/django/tests/regressiontests/dispatch/tests/test_dispatcher.py123
-rw-r--r--parts/django/tests/regressiontests/dispatch/tests/test_saferef.py79
-rw-r--r--parts/django/tests/regressiontests/expressions_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/expressions_regress/models.py12
-rw-r--r--parts/django/tests/regressiontests/expressions_regress/tests.py195
-rw-r--r--parts/django/tests/regressiontests/extra_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/extra_regress/models.py40
-rw-r--r--parts/django/tests/regressiontests/extra_regress/tests.py314
-rw-r--r--parts/django/tests/regressiontests/file_storage/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/file_storage/models.py0
-rw-r--r--parts/django/tests/regressiontests/file_storage/test.pngbin0 -> 482 bytes
-rw-r--r--parts/django/tests/regressiontests/file_storage/test1.pngbin0 -> 480 bytes
-rw-r--r--parts/django/tests/regressiontests/file_storage/tests.py382
-rw-r--r--parts/django/tests/regressiontests/file_uploads/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/file_uploads/models.py10
-rw-r--r--parts/django/tests/regressiontests/file_uploads/tests.py305
-rw-r--r--parts/django/tests/regressiontests/file_uploads/uploadhandler.py34
-rw-r--r--parts/django/tests/regressiontests/file_uploads/urls.py13
-rw-r--r--parts/django/tests/regressiontests/file_uploads/views.py114
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/absolute.json9
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/animal.xml9
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn1
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml7
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json83
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/empty.json1
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json32
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json4
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json18
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml23
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json25
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml16
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/pretty.xml11
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/sequence.json12
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/fixtures/thingy.json9
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/models.py225
-rw-r--r--parts/django/tests/regressiontests/fixtures_regress/tests.py611
-rw-r--r--parts/django/tests/regressiontests/forms/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/ar.py99
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/at.py45
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/au.py50
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/br.py144
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/ca.py95
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/ch.py75
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/cl.py56
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/cz.py126
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/de.py49
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/es.py172
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/fi.py382
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/fr.py145
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/generic.py88
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/id.py181
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/ie.py43
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/is_.py199
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/it.py70
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/jp.py73
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/kw.py16
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/nl.py62
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/pl.py462
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/pt.py32
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/ro.py142
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/se.py331
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/sk.py116
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/uk.py30
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/us.py126
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/utils.py51
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/uy.py52
-rw-r--r--parts/django/tests/regressiontests/forms/localflavor/za.py29
-rw-r--r--parts/django/tests/regressiontests/forms/localflavortests.py37
-rw-r--r--parts/django/tests/regressiontests/forms/models.py74
-rw-r--r--parts/django/tests/regressiontests/forms/tests/__init__.py43
-rw-r--r--parts/django/tests/regressiontests/forms/tests/error_messages.py253
-rw-r--r--parts/django/tests/regressiontests/forms/tests/extra.py610
-rw-r--r--parts/django/tests/regressiontests/forms/tests/fields.py862
-rw-r--r--parts/django/tests/regressiontests/forms/tests/forms.py1700
-rw-r--r--parts/django/tests/regressiontests/forms/tests/formsets.py763
-rw-r--r--parts/django/tests/regressiontests/forms/tests/input_formats.py894
-rw-r--r--parts/django/tests/regressiontests/forms/tests/media.py460
-rw-r--r--parts/django/tests/regressiontests/forms/tests/models.py169
-rw-r--r--parts/django/tests/regressiontests/forms/tests/regressions.py145
-rw-r--r--parts/django/tests/regressiontests/forms/tests/util.py57
-rw-r--r--parts/django/tests/regressiontests/forms/tests/validators.py17
-rw-r--r--parts/django/tests/regressiontests/forms/tests/widgets.py1070
-rw-r--r--parts/django/tests/regressiontests/formwizard/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/formwizard/forms.py18
-rw-r--r--parts/django/tests/regressiontests/formwizard/models.py0
-rw-r--r--parts/django/tests/regressiontests/formwizard/templates/forms/wizard.html13
-rw-r--r--parts/django/tests/regressiontests/formwizard/tests.py59
-rw-r--r--parts/django/tests/regressiontests/formwizard/urls.py6
-rw-r--r--parts/django/tests/regressiontests/generic_inline_admin/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/generic_inline_admin/fixtures/users.xml17
-rw-r--r--parts/django/tests/regressiontests/generic_inline_admin/models.py108
-rw-r--r--parts/django/tests/regressiontests/generic_inline_admin/tests.py214
-rw-r--r--parts/django/tests/regressiontests/generic_inline_admin/urls.py6
-rw-r--r--parts/django/tests/regressiontests/generic_relations_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/generic_relations_regress/models.py79
-rw-r--r--parts/django/tests/regressiontests/generic_relations_regress/tests.py74
-rw-r--r--parts/django/tests/regressiontests/get_or_create_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/get_or_create_regress/models.py13
-rw-r--r--parts/django/tests/regressiontests/get_or_create_regress/tests.py53
-rw-r--r--parts/django/tests/regressiontests/httpwrappers/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/httpwrappers/models.py0
-rw-r--r--parts/django/tests/regressiontests/httpwrappers/tests.py266
-rw-r--r--parts/django/tests/regressiontests/humanize/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/humanize/models.py0
-rw-r--r--parts/django/tests/regressiontests/humanize/tests.py76
-rw-r--r--parts/django/tests/regressiontests/i18n/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/i18n/forms.py22
-rw-r--r--parts/django/tests/regressiontests/i18n/models.py12
-rw-r--r--parts/django/tests/regressiontests/i18n/other/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/i18n/other/locale/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mobin0 -> 469 bytes
-rw-r--r--parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po22
-rw-r--r--parts/django/tests/regressiontests/i18n/other/locale/de/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/i18n/other/locale/de/formats.py0
-rw-r--r--parts/django/tests/regressiontests/i18n/resolution/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mobin0 -> 460 bytes
-rw-r--r--parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po22
-rw-r--r--parts/django/tests/regressiontests/i18n/resolution/models.py1
-rw-r--r--parts/django/tests/regressiontests/i18n/tests.py667
-rw-r--r--parts/django/tests/regressiontests/initial_sql_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/initial_sql_regress/models.py11
-rw-r--r--parts/django/tests/regressiontests/initial_sql_regress/sql/simple.sql8
-rw-r--r--parts/django/tests/regressiontests/initial_sql_regress/tests.py8
-rw-r--r--parts/django/tests/regressiontests/inline_formsets/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/inline_formsets/models.py28
-rw-r--r--parts/django/tests/regressiontests/inline_formsets/tests.py163
-rw-r--r--parts/django/tests/regressiontests/introspection/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/introspection/models.py21
-rw-r--r--parts/django/tests/regressiontests/introspection/tests.py111
-rw-r--r--parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.mobin0 -> 464 bytes
-rw-r--r--parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.po22
-rw-r--r--parts/django/tests/regressiontests/localflavor/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/localflavor/models.py0
-rw-r--r--parts/django/tests/regressiontests/localflavor/tests.py5
-rw-r--r--parts/django/tests/regressiontests/localflavor/us/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/localflavor/us/forms.py7
-rw-r--r--parts/django/tests/regressiontests/localflavor/us/models.py13
-rw-r--r--parts/django/tests/regressiontests/localflavor/us/tests.py82
-rw-r--r--parts/django/tests/regressiontests/m2m_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/m2m_regress/models.py58
-rw-r--r--parts/django/tests/regressiontests/m2m_regress/tests.py82
-rw-r--r--parts/django/tests/regressiontests/m2m_through_regress/__init__.py2
-rw-r--r--parts/django/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json34
-rw-r--r--parts/django/tests/regressiontests/m2m_through_regress/models.py55
-rw-r--r--parts/django/tests/regressiontests/m2m_through_regress/tests.py128
-rw-r--r--parts/django/tests/regressiontests/mail/__init__.py2
-rw-r--r--parts/django/tests/regressiontests/mail/custombackend.py15
-rw-r--r--parts/django/tests/regressiontests/mail/models.py1
-rw-r--r--parts/django/tests/regressiontests/mail/tests.py375
-rw-r--r--parts/django/tests/regressiontests/makemessages/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/makemessages/extraction.py109
-rw-r--r--parts/django/tests/regressiontests/makemessages/ignore_dir/ignored.html2
-rw-r--r--parts/django/tests/regressiontests/makemessages/javascript.js4
-rw-r--r--parts/django/tests/regressiontests/makemessages/locale/dummy0
-rw-r--r--parts/django/tests/regressiontests/makemessages/models.py0
-rw-r--r--parts/django/tests/regressiontests/makemessages/templates/test.html4
-rw-r--r--parts/django/tests/regressiontests/makemessages/tests.py40
-rw-r--r--parts/django/tests/regressiontests/managers_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/managers_regress/models.py100
-rw-r--r--parts/django/tests/regressiontests/managers_regress/tests.py54
-rw-r--r--parts/django/tests/regressiontests/many_to_one_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/many_to_one_regress/models.py46
-rw-r--r--parts/django/tests/regressiontests/many_to_one_regress/tests.py105
-rw-r--r--parts/django/tests/regressiontests/max_lengths/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/max_lengths/models.py13
-rw-r--r--parts/django/tests/regressiontests/max_lengths/tests.py36
-rw-r--r--parts/django/tests/regressiontests/middleware/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/middleware/extra_urls.py7
-rw-r--r--parts/django/tests/regressiontests/middleware/models.py1
-rw-r--r--parts/django/tests/regressiontests/middleware/tests.py249
-rw-r--r--parts/django/tests/regressiontests/middleware/urls.py7
-rw-r--r--parts/django/tests/regressiontests/middleware_exceptions/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/middleware_exceptions/models.py1
-rw-r--r--parts/django/tests/regressiontests/middleware_exceptions/tests.py40
-rw-r--r--parts/django/tests/regressiontests/middleware_exceptions/urls.py8
-rw-r--r--parts/django/tests/regressiontests/middleware_exceptions/views.py4
-rw-r--r--parts/django/tests/regressiontests/model_fields/4x8.pngbin0 -> 87 bytes
-rw-r--r--parts/django/tests/regressiontests/model_fields/8x4.pngbin0 -> 87 bytes
-rw-r--r--parts/django/tests/regressiontests/model_fields/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/model_fields/imagefield.py420
-rw-r--r--parts/django/tests/regressiontests/model_fields/models.py154
-rw-r--r--parts/django/tests/regressiontests/model_fields/tests.py313
-rw-r--r--parts/django/tests/regressiontests/model_forms_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/model_forms_regress/models.py59
-rw-r--r--parts/django/tests/regressiontests/model_forms_regress/tests.py311
-rw-r--r--parts/django/tests/regressiontests/model_formsets_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/model_formsets_regress/models.py19
-rw-r--r--parts/django/tests/regressiontests/model_formsets_regress/tests.py218
-rw-r--r--parts/django/tests/regressiontests/model_inheritance_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/model_inheritance_regress/models.py148
-rw-r--r--parts/django/tests/regressiontests/model_inheritance_regress/tests.py388
-rw-r--r--parts/django/tests/regressiontests/model_inheritance_select_related/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/model_inheritance_select_related/models.py29
-rw-r--r--parts/django/tests/regressiontests/model_inheritance_select_related/tests.py29
-rw-r--r--parts/django/tests/regressiontests/model_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/model_regress/models.py59
-rw-r--r--parts/django/tests/regressiontests/model_regress/tests.py175
-rw-r--r--parts/django/tests/regressiontests/modeladmin/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/modeladmin/models.py36
-rw-r--r--parts/django/tests/regressiontests/modeladmin/tests.py1226
-rw-r--r--parts/django/tests/regressiontests/multiple_database/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/multiple_database/fixtures/multidb-common.json10
-rw-r--r--parts/django/tests/regressiontests/multiple_database/fixtures/multidb.default.json26
-rw-r--r--parts/django/tests/regressiontests/multiple_database/fixtures/multidb.other.json26
-rw-r--r--parts/django/tests/regressiontests/multiple_database/fixtures/pets.json18
-rw-r--r--parts/django/tests/regressiontests/multiple_database/models.py76
-rw-r--r--parts/django/tests/regressiontests/multiple_database/tests.py1681
-rw-r--r--parts/django/tests/regressiontests/null_fk/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/null_fk/models.py33
-rw-r--r--parts/django/tests/regressiontests/null_fk/tests.py42
-rw-r--r--parts/django/tests/regressiontests/null_fk_ordering/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/null_fk_ordering/models.py49
-rw-r--r--parts/django/tests/regressiontests/null_fk_ordering/tests.py39
-rw-r--r--parts/django/tests/regressiontests/null_queries/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/null_queries/models.py25
-rw-r--r--parts/django/tests/regressiontests/null_queries/tests.py69
-rw-r--r--parts/django/tests/regressiontests/one_to_one_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/one_to_one_regress/models.py43
-rw-r--r--parts/django/tests/regressiontests/one_to_one_regress/tests.py130
-rw-r--r--parts/django/tests/regressiontests/pagination_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/pagination_regress/models.py1
-rw-r--r--parts/django/tests/regressiontests/pagination_regress/tests.py157
-rw-r--r--parts/django/tests/regressiontests/queries/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/queries/models.py276
-rw-r--r--parts/django/tests/regressiontests/queries/tests.py1586
-rw-r--r--parts/django/tests/regressiontests/queryset_pickle/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/queryset_pickle/models.py34
-rw-r--r--parts/django/tests/regressiontests/queryset_pickle/tests.py36
-rw-r--r--parts/django/tests/regressiontests/requests/__init__.py3
-rw-r--r--parts/django/tests/regressiontests/requests/models.py1
-rw-r--r--parts/django/tests/regressiontests/requests/tests.py59
-rw-r--r--parts/django/tests/regressiontests/reverse_single_related/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/reverse_single_related/models.py12
-rw-r--r--parts/django/tests/regressiontests/reverse_single_related/tests.py36
-rw-r--r--parts/django/tests/regressiontests/select_related_onetoone/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/select_related_onetoone/models.py54
-rw-r--r--parts/django/tests/regressiontests/select_related_onetoone/tests.py94
-rw-r--r--parts/django/tests/regressiontests/select_related_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/select_related_regress/models.py86
-rw-r--r--parts/django/tests/regressiontests/select_related_regress/tests.py134
-rw-r--r--parts/django/tests/regressiontests/serializers_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/serializers_regress/models.py266
-rw-r--r--parts/django/tests/regressiontests/serializers_regress/tests.py421
-rw-r--r--parts/django/tests/regressiontests/servers/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/servers/models.py0
-rw-r--r--parts/django/tests/regressiontests/servers/tests.py68
-rw-r--r--parts/django/tests/regressiontests/settings_tests/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/settings_tests/models.py0
-rw-r--r--parts/django/tests/regressiontests/settings_tests/tests.py17
-rw-r--r--parts/django/tests/regressiontests/signals_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/signals_regress/models.py14
-rw-r--r--parts/django/tests/regressiontests/signals_regress/tests.py96
-rw-r--r--parts/django/tests/regressiontests/sites_framework/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/sites_framework/models.py36
-rw-r--r--parts/django/tests/regressiontests/sites_framework/tests.py34
-rw-r--r--parts/django/tests/regressiontests/special_headers/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/special_headers/fixtures/data.xml20
-rw-r--r--parts/django/tests/regressiontests/special_headers/models.py4
-rw-r--r--parts/django/tests/regressiontests/special_headers/templates/special_headers/article_detail.html1
-rw-r--r--parts/django/tests/regressiontests/special_headers/tests.py40
-rw-r--r--parts/django/tests/regressiontests/special_headers/urls.py10
-rw-r--r--parts/django/tests/regressiontests/special_headers/views.py10
-rw-r--r--parts/django/tests/regressiontests/string_lookup/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/string_lookup/models.py45
-rw-r--r--parts/django/tests/regressiontests/string_lookup/tests.py78
-rw-r--r--parts/django/tests/regressiontests/syndication/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/syndication/feeds.py142
-rw-r--r--parts/django/tests/regressiontests/syndication/fixtures/feeddata.json42
-rw-r--r--parts/django/tests/regressiontests/syndication/models.py23
-rw-r--r--parts/django/tests/regressiontests/syndication/templates/syndication/description.html1
-rw-r--r--parts/django/tests/regressiontests/syndication/templates/syndication/title.html1
-rw-r--r--parts/django/tests/regressiontests/syndication/tests.py356
-rw-r--r--parts/django/tests/regressiontests/syndication/urls.py24
-rw-r--r--parts/django/tests/regressiontests/templates/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/templates/context.py17
-rw-r--r--parts/django/tests/regressiontests/templates/custom.py12
-rwxr-xr-xparts/django/tests/regressiontests/templates/eggs/tagsegg.eggbin0 -> 2581 bytes
-rw-r--r--parts/django/tests/regressiontests/templates/filters.py350
-rw-r--r--parts/django/tests/regressiontests/templates/loaders.py150
-rw-r--r--parts/django/tests/regressiontests/templates/models.py0
-rw-r--r--parts/django/tests/regressiontests/templates/nodelist.py30
-rw-r--r--parts/django/tests/regressiontests/templates/parser.py84
-rw-r--r--parts/django/tests/regressiontests/templates/smartif.py53
-rw-r--r--parts/django/tests/regressiontests/templates/templates/broken_base.html1
-rw-r--r--parts/django/tests/regressiontests/templates/templates/first/test.html1
-rw-r--r--parts/django/tests/regressiontests/templates/templates/second/test.html1
-rwxr-xr-xparts/django/tests/regressiontests/templates/templates/test_extends_error.html1
-rw-r--r--parts/django/tests/regressiontests/templates/templatetags/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/templates/templatetags/broken_tag.py1
-rw-r--r--parts/django/tests/regressiontests/templates/templatetags/custom.py11
-rw-r--r--parts/django/tests/regressiontests/templates/tests.py1389
-rw-r--r--parts/django/tests/regressiontests/templates/unicode.py30
-rw-r--r--parts/django/tests/regressiontests/templates/urls.py17
-rw-r--r--parts/django/tests/regressiontests/templates/views.py13
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/bad_templates/404.html3
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/fixtures/testdata.json56
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/models.py864
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/session.py30
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/templates/unicode.html5
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/urls.py29
-rw-r--r--parts/django/tests/regressiontests/test_client_regress/views.py93
-rw-r--r--parts/django/tests/regressiontests/test_runner/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/test_runner/models.py0
-rw-r--r--parts/django/tests/regressiontests/test_runner/tests.py120
-rw-r--r--parts/django/tests/regressiontests/test_utils/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/test_utils/models.py0
-rw-r--r--parts/django/tests/regressiontests/test_utils/tests.py72
-rw-r--r--parts/django/tests/regressiontests/text/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/text/models.py0
-rw-r--r--parts/django/tests/regressiontests/text/tests.py81
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/extra_urls.py13
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py13
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/included_urls.py8
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/included_urls2.py14
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/middleware.py11
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/models.py0
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/namespace_urls.py38
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/no_urls.py2
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/tests.py330
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py12
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py9
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/urls.py63
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py8
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py9
-rw-r--r--parts/django/tests/regressiontests/urlpatterns_reverse/views.py8
-rw-r--r--parts/django/tests/regressiontests/utils/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/utils/checksums.py29
-rw-r--r--parts/django/tests/regressiontests/utils/datastructures.py256
-rw-r--r--parts/django/tests/regressiontests/utils/dateformat.py129
-rw-r--r--parts/django/tests/regressiontests/utils/datetime_safe.py42
-rw-r--r--parts/django/tests/regressiontests/utils/decorators.py19
-rw-r--r--parts/django/tests/regressiontests/utils/eggs/test_egg.eggbin0 -> 4844 bytes
-rw-r--r--parts/django/tests/regressiontests/utils/feedgenerator.py63
-rw-r--r--parts/django/tests/regressiontests/utils/functional.py10
-rw-r--r--parts/django/tests/regressiontests/utils/html.py111
-rw-r--r--parts/django/tests/regressiontests/utils/models.py1
-rw-r--r--parts/django/tests/regressiontests/utils/module_loading.py114
-rw-r--r--parts/django/tests/regressiontests/utils/simplelazyobject.py77
-rw-r--r--parts/django/tests/regressiontests/utils/termcolors.py149
-rw-r--r--parts/django/tests/regressiontests/utils/test_module/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/utils/test_module/bad_module.py3
-rw-r--r--parts/django/tests/regressiontests/utils/test_module/good_module.py1
-rw-r--r--parts/django/tests/regressiontests/utils/tests.py18
-rw-r--r--parts/django/tests/regressiontests/utils/text.py20
-rw-r--r--parts/django/tests/regressiontests/utils/timesince.py107
-rw-r--r--parts/django/tests/regressiontests/utils/tzinfo.py18
-rw-r--r--parts/django/tests/regressiontests/utils/urls.py8
-rw-r--r--parts/django/tests/regressiontests/utils/views.py17
-rw-r--r--parts/django/tests/regressiontests/views/__init__.py10
-rw-r--r--parts/django/tests/regressiontests/views/app0/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mobin0 -> 482 bytes
-rw-r--r--parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po20
-rw-r--r--parts/django/tests/regressiontests/views/app1/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mobin0 -> 482 bytes
-rw-r--r--parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po20
-rw-r--r--parts/django/tests/regressiontests/views/app2/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mobin0 -> 482 bytes
-rw-r--r--parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po20
-rw-r--r--parts/django/tests/regressiontests/views/app3/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mobin0 -> 483 bytes
-rw-r--r--parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po20
-rw-r--r--parts/django/tests/regressiontests/views/app4/__init__.py1
-rw-r--r--parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mobin0 -> 483 bytes
-rw-r--r--parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po20
-rw-r--r--parts/django/tests/regressiontests/views/fixtures/testdata.json75
-rw-r--r--parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mobin0 -> 490 bytes
-rw-r--r--parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po25
-rw-r--r--parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mobin0 -> 484 bytes
-rw-r--r--parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po24
-rw-r--r--parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mobin0 -> 489 bytes
-rw-r--r--parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po24
-rw-r--r--parts/django/tests/regressiontests/views/media/file.txt1
-rw-r--r--parts/django/tests/regressiontests/views/media/file.txt.gzbin0 -> 51 bytes
-rw-r--r--parts/django/tests/regressiontests/views/media/file.unknown1
-rw-r--r--parts/django/tests/regressiontests/views/models.py49
-rw-r--r--parts/django/tests/regressiontests/views/templates/debug/template_exception.html2
-rw-r--r--parts/django/tests/regressiontests/views/templatetags/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/views/templatetags/debugtags.py10
-rw-r--r--parts/django/tests/regressiontests/views/tests/__init__.py7
-rw-r--r--parts/django/tests/regressiontests/views/tests/debug.py50
-rw-r--r--parts/django/tests/regressiontests/views/tests/defaults.py85
-rw-r--r--parts/django/tests/regressiontests/views/tests/generic/__init__.py0
-rw-r--r--parts/django/tests/regressiontests/views/tests/generic/create_update.py211
-rw-r--r--parts/django/tests/regressiontests/views/tests/generic/date_based.py140
-rw-r--r--parts/django/tests/regressiontests/views/tests/i18n.py149
-rw-r--r--parts/django/tests/regressiontests/views/tests/specials.py35
-rw-r--r--parts/django/tests/regressiontests/views/tests/static.py77
-rw-r--r--parts/django/tests/regressiontests/views/urls.py131
-rw-r--r--parts/django/tests/regressiontests/views/views.py59
560 files changed, 45642 insertions, 0 deletions
diff --git a/parts/django/tests/regressiontests/__init__.py b/parts/django/tests/regressiontests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_changelist/__init__.py b/parts/django/tests/regressiontests/admin_changelist/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_changelist/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_changelist/models.py b/parts/django/tests/regressiontests/admin_changelist/models.py
new file mode 100644
index 0000000..f030a78
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_changelist/models.py
@@ -0,0 +1,9 @@
+from django.db import models
+from django.contrib import admin
+
+class Parent(models.Model):
+ name = models.CharField(max_length=128)
+
+class Child(models.Model):
+ parent = models.ForeignKey(Parent, editable=False)
+ name = models.CharField(max_length=30, blank=True) \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_changelist/tests.py b/parts/django/tests/regressiontests/admin_changelist/tests.py
new file mode 100644
index 0000000..96b36f8
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_changelist/tests.py
@@ -0,0 +1,103 @@
+from django.contrib import admin
+from django.contrib.admin.options import IncorrectLookupParameters
+from django.contrib.admin.views.main import ChangeList
+from django.template import Context, Template
+from django.test import TransactionTestCase
+from regressiontests.admin_changelist.models import Child, Parent
+
+class ChangeListTests(TransactionTestCase):
+ def test_select_related_preserved(self):
+ """
+ Regression test for #10348: ChangeList.get_query_set() shouldn't
+ overwrite a custom select_related provided by ModelAdmin.queryset().
+ """
+ m = ChildAdmin(Child, admin.site)
+ cl = ChangeList(MockRequest(), Child, m.list_display, m.list_display_links,
+ m.list_filter, m.date_hierarchy, m.search_fields,
+ m.list_select_related, m.list_per_page, m.list_editable, m)
+ self.assertEqual(cl.query_set.query.select_related, {'parent': {'name': {}}})
+
+ def test_result_list_html(self):
+ """
+ Verifies that inclusion tag result_list generates a table when with
+ default ModelAdmin settings.
+ """
+ new_parent = Parent.objects.create(name='parent')
+ new_child = Child.objects.create(name='name', parent=new_parent)
+ request = MockRequest()
+ m = ChildAdmin(Child, admin.site)
+ cl = ChangeList(request, Child, m.list_display, m.list_display_links,
+ m.list_filter, m.date_hierarchy, m.search_fields,
+ m.list_select_related, m.list_per_page, m.list_editable, m)
+ cl.formset = None
+ template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
+ context = Context({'cl': cl})
+ table_output = template.render(context)
+ row_html = '<tbody><tr class="row1"><td><input type="checkbox" class="action-select" value="1" name="_selected_action" /></td><th><a href="1/">name</a></th><td>Parent object</td></tr></tbody>'
+ self.assertFalse(table_output.find(row_html) == -1,
+ 'Failed to find expected row element: %s' % table_output)
+
+ def test_result_list_editable_html(self):
+ """
+ Regression tests for #11791: Inclusion tag result_list generates a
+ table and this checks that the items are nested within the table
+ element tags.
+ Also a regression test for #13599, verifies that hidden fields
+ when list_editable is enabled are rendered in a div outside the
+ table.
+ """
+ new_parent = Parent.objects.create(name='parent')
+ new_child = Child.objects.create(name='name', parent=new_parent)
+ request = MockRequest()
+ m = ChildAdmin(Child, admin.site)
+
+ # Test with list_editable fields
+ m.list_display = ['id', 'name', 'parent']
+ m.list_display_links = ['id']
+ m.list_editable = ['name']
+ cl = ChangeList(request, Child, m.list_display, m.list_display_links,
+ m.list_filter, m.date_hierarchy, m.search_fields,
+ m.list_select_related, m.list_per_page, m.list_editable, m)
+ FormSet = m.get_changelist_formset(request)
+ cl.formset = FormSet(queryset=cl.result_list)
+ template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
+ context = Context({'cl': cl})
+ table_output = template.render(context)
+ # make sure that hidden fields are in the correct place
+ hiddenfields_div = '<div class="hiddenfields"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></div>'
+ self.assertFalse(table_output.find(hiddenfields_div) == -1,
+ 'Failed to find hidden fields in: %s' % table_output)
+ # make sure that list editable fields are rendered in divs correctly
+ editable_name_field = '<input name="form-0-name" value="name" class="vTextField" maxlength="30" type="text" id="id_form-0-name" />'
+ self.assertFalse('<td>%s</td>' % editable_name_field == -1,
+ 'Failed to find "name" list_editable field in: %s' % table_output)
+
+ def test_result_list_editable(self):
+ """
+ Regression test for #14312: list_editable with pagination
+ """
+
+ new_parent = Parent.objects.create(name='parent')
+ for i in range(200):
+ new_child = Child.objects.create(name='name %s' % i, parent=new_parent)
+ request = MockRequest()
+ request.GET['p'] = -1 # Anything outside range
+ m = ChildAdmin(Child, admin.site)
+
+ # Test with list_editable fields
+ m.list_display = ['id', 'name', 'parent']
+ m.list_display_links = ['id']
+ m.list_editable = ['name']
+ self.assertRaises(IncorrectLookupParameters, lambda: \
+ ChangeList(request, Child, m.list_display, m.list_display_links,
+ m.list_filter, m.date_hierarchy, m.search_fields,
+ m.list_select_related, m.list_per_page, m.list_editable, m))
+
+
+class ChildAdmin(admin.ModelAdmin):
+ list_display = ['name', 'parent']
+ def queryset(self, request):
+ return super(ChildAdmin, self).queryset(request).select_related("parent__name")
+
+class MockRequest(object):
+ GET = {}
diff --git a/parts/django/tests/regressiontests/admin_inlines/__init__.py b/parts/django/tests/regressiontests/admin_inlines/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_inlines/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml b/parts/django/tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml
new file mode 100644
index 0000000..aba8f4a
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_inlines/models.py b/parts/django/tests/regressiontests/admin_inlines/models.py
new file mode 100644
index 0000000..4e5c4e3
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_inlines/models.py
@@ -0,0 +1,125 @@
+"""
+Testing of admin inline formsets.
+
+"""
+from django.db import models
+from django.contrib import admin
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+
+class Parent(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return self.name
+
+class Teacher(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return self.name
+
+class Child(models.Model):
+ name = models.CharField(max_length=50)
+ teacher = models.ForeignKey(Teacher)
+
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ parent = generic.GenericForeignKey()
+
+ def __unicode__(self):
+ return u'I am %s, a child of %s' % (self.name, self.parent)
+
+class Book(models.Model):
+ name = models.CharField(max_length=50)
+
+class Author(models.Model):
+ name = models.CharField(max_length=50)
+ books = models.ManyToManyField(Book)
+
+class BookInline(admin.TabularInline):
+ model = Author.books.through
+
+class AuthorAdmin(admin.ModelAdmin):
+ inlines = [BookInline]
+
+admin.site.register(Author, AuthorAdmin)
+
+class Holder(models.Model):
+ dummy = models.IntegerField()
+
+
+class Inner(models.Model):
+ dummy = models.IntegerField()
+ holder = models.ForeignKey(Holder)
+ readonly = models.CharField("Inner readonly label", max_length=1)
+
+
+class InnerInline(admin.StackedInline):
+ model = Inner
+ can_delete = False
+ readonly_fields = ('readonly',) # For bug #13174 tests.
+
+
+class Holder2(models.Model):
+ dummy = models.IntegerField()
+
+
+class Inner2(models.Model):
+ dummy = models.IntegerField()
+ holder = models.ForeignKey(Holder2)
+
+class HolderAdmin(admin.ModelAdmin):
+
+ class Media:
+ js = ('my_awesome_admin_scripts.js',)
+
+class InnerInline2(admin.StackedInline):
+ model = Inner2
+
+ class Media:
+ js = ('my_awesome_inline_scripts.js',)
+
+class Holder3(models.Model):
+ dummy = models.IntegerField()
+
+
+class Inner3(models.Model):
+ dummy = models.IntegerField()
+ holder = models.ForeignKey(Holder3)
+
+class InnerInline3(admin.StackedInline):
+ model = Inner3
+
+ class Media:
+ js = ('my_awesome_inline_scripts.js',)
+
+# Test bug #12561 and #12778
+# only ModelAdmin media
+admin.site.register(Holder, HolderAdmin, inlines=[InnerInline])
+# ModelAdmin and Inline media
+admin.site.register(Holder2, HolderAdmin, inlines=[InnerInline2])
+# only Inline media
+admin.site.register(Holder3, inlines=[InnerInline3])
+
+# Models for #12749
+
+class Person(models.Model):
+ firstname = models.CharField(max_length=15)
+
+class OutfitItem(models.Model):
+ name = models.CharField(max_length=15)
+
+class Fashionista(models.Model):
+ person = models.OneToOneField(Person, primary_key=True)
+ weaknesses = models.ManyToManyField(OutfitItem, through='ShoppingWeakness', blank=True)
+
+class ShoppingWeakness(models.Model):
+ fashionista = models.ForeignKey(Fashionista)
+ item = models.ForeignKey(OutfitItem)
+
+class InlineWeakness(admin.TabularInline):
+ model = ShoppingWeakness
+ extra = 1
+
+admin.site.register(Fashionista, inlines=[InlineWeakness])
diff --git a/parts/django/tests/regressiontests/admin_inlines/tests.py b/parts/django/tests/regressiontests/admin_inlines/tests.py
new file mode 100644
index 0000000..b10474d
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_inlines/tests.py
@@ -0,0 +1,121 @@
+from django.contrib.admin.helpers import InlineAdminForm
+from django.contrib.contenttypes.models import ContentType
+from django.test import TestCase
+
+# local test models
+from models import (Holder, Inner, InnerInline, Holder2, Inner2, Holder3,
+ Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child)
+
+
+class TestInline(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ holder = Holder(dummy=13)
+ holder.save()
+ Inner(dummy=42, holder=holder).save()
+ self.change_url = '/test_admin/admin/admin_inlines/holder/%i/' % holder.id
+
+ result = self.client.login(username='super', password='secret')
+ self.assertEqual(result, True)
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_can_delete(self):
+ """
+ can_delete should be passed to inlineformset factory.
+ """
+ response = self.client.get(self.change_url)
+ inner_formset = response.context[-1]['inline_admin_formsets'][0].formset
+ expected = InnerInline.can_delete
+ actual = inner_formset.can_delete
+ self.assertEqual(expected, actual, 'can_delete must be equal')
+
+ def test_readonly_stacked_inline_label(self):
+ """Bug #13174."""
+ holder = Holder.objects.create(dummy=42)
+ inner = Inner.objects.create(holder=holder, dummy=42, readonly='')
+ response = self.client.get('/test_admin/admin/admin_inlines/holder/%i/'
+ % holder.id)
+ self.assertContains(response, '<label>Inner readonly label:</label>')
+
+ def test_many_to_many_inlines(self):
+ "Autogenerated many-to-many inlines are displayed correctly (#13407)"
+ response = self.client.get('/test_admin/admin/admin_inlines/author/add/')
+ # The heading for the m2m inline block uses the right text
+ self.assertContains(response, '<h2>Author-book relationships</h2>')
+ # The "add another" label is correct
+ self.assertContains(response, 'Add another Author-Book Relationship')
+ # The '+' is dropped from the autogenerated form prefix (Author_books+)
+ self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"')
+
+ def test_inline_primary(self):
+ person = Person.objects.create(firstname='Imelda')
+ item = OutfitItem.objects.create(name='Shoes')
+ # Imelda likes shoes, but can't cary her own bags.
+ data = {
+ 'shoppingweakness_set-TOTAL_FORMS': 1,
+ 'shoppingweakness_set-INITIAL_FORMS': 0,
+ 'shoppingweakness_set-MAX_NUM_FORMS': 0,
+ '_save': u'Save',
+ 'person': person.id,
+ 'max_weight': 0,
+ 'shoppingweakness_set-0-item': item.id,
+ }
+ response = self.client.post('/test_admin/admin/admin_inlines/fashionista/add/', data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1)
+
+class TestInlineMedia(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+
+ result = self.client.login(username='super', password='secret')
+ self.assertEqual(result, True)
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_inline_media_only_base(self):
+ holder = Holder(dummy=13)
+ holder.save()
+ Inner(dummy=42, holder=holder).save()
+ change_url = '/test_admin/admin/admin_inlines/holder/%i/' % holder.id
+ response = self.client.get(change_url)
+ self.assertContains(response, 'my_awesome_admin_scripts.js')
+
+ def test_inline_media_only_inline(self):
+ holder = Holder3(dummy=13)
+ holder.save()
+ Inner3(dummy=42, holder=holder).save()
+ change_url = '/test_admin/admin/admin_inlines/holder3/%i/' % holder.id
+ response = self.client.get(change_url)
+ self.assertContains(response, 'my_awesome_inline_scripts.js')
+
+ def test_all_inline_media(self):
+ holder = Holder2(dummy=13)
+ holder.save()
+ Inner2(dummy=42, holder=holder).save()
+ change_url = '/test_admin/admin/admin_inlines/holder2/%i/' % holder.id
+ response = self.client.get(change_url)
+ self.assertContains(response, 'my_awesome_admin_scripts.js')
+ self.assertContains(response, 'my_awesome_inline_scripts.js')
+
+class TestInlineAdminForm(TestCase):
+
+ def test_immutable_content_type(self):
+ """Regression for #9362
+ The problem depends only on InlineAdminForm and its "original"
+ argument, so we can safely set the other arguments to None/{}. We just
+ need to check that the content_type argument of Child isn't altered by
+ the internals of the inline form."""
+
+ sally = Teacher.objects.create(name='Sally')
+ john = Parent.objects.create(name='John')
+ joe = Child.objects.create(name='Joe', teacher=sally, parent=john)
+
+ iaf = InlineAdminForm(None, None, {}, {}, joe)
+ parent_ct = ContentType.objects.get_for_model(Parent)
+ self.assertEqual(iaf.original.content_type, parent_ct)
diff --git a/parts/django/tests/regressiontests/admin_ordering/__init__.py b/parts/django/tests/regressiontests/admin_ordering/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_ordering/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_ordering/models.py b/parts/django/tests/regressiontests/admin_ordering/models.py
new file mode 100644
index 0000000..ad63685
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_ordering/models.py
@@ -0,0 +1,10 @@
+# coding: utf-8
+from django.db import models
+
+class Band(models.Model):
+ name = models.CharField(max_length=100)
+ bio = models.TextField()
+ rank = models.IntegerField()
+
+ class Meta:
+ ordering = ('name',)
diff --git a/parts/django/tests/regressiontests/admin_ordering/tests.py b/parts/django/tests/regressiontests/admin_ordering/tests.py
new file mode 100644
index 0000000..f63f202
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_ordering/tests.py
@@ -0,0 +1,39 @@
+from django.test import TestCase
+from django.contrib.admin.options import ModelAdmin
+
+from models import Band
+
+class TestAdminOrdering(TestCase):
+ """
+ Let's make sure that ModelAdmin.queryset uses the ordering we define in
+ ModelAdmin rather that ordering defined in the model's inner Meta
+ class.
+ """
+
+ def setUp(self):
+ b1 = Band(name='Aerosmith', bio='', rank=3)
+ b1.save()
+ b2 = Band(name='Radiohead', bio='', rank=1)
+ b2.save()
+ b3 = Band(name='Van Halen', bio='', rank=2)
+ b3.save()
+
+ def test_default_ordering(self):
+ """
+ The default ordering should be by name, as specified in the inner Meta
+ class.
+ """
+ ma = ModelAdmin(Band, None)
+ names = [b.name for b in ma.queryset(None)]
+ self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names)
+
+ def test_specified_ordering(self):
+ """
+ Let's use a custom ModelAdmin that changes the ordering, and make sure
+ it actually changes.
+ """
+ class BandAdmin(ModelAdmin):
+ ordering = ('rank',) # default ordering is ('name',)
+ ma = BandAdmin(Band, None)
+ names = [b.name for b in ma.queryset(None)]
+ self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names)
diff --git a/parts/django/tests/regressiontests/admin_registration/__init__.py b/parts/django/tests/regressiontests/admin_registration/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_registration/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_registration/models.py b/parts/django/tests/regressiontests/admin_registration/models.py
new file mode 100644
index 0000000..4a2d4e9
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_registration/models.py
@@ -0,0 +1,11 @@
+"""
+Tests for various ways of registering models with the admin site.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ name = models.CharField(max_length=200)
+
+class Place(models.Model):
+ name = models.CharField(max_length=200)
diff --git a/parts/django/tests/regressiontests/admin_registration/tests.py b/parts/django/tests/regressiontests/admin_registration/tests.py
new file mode 100644
index 0000000..e2a5d7e
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_registration/tests.py
@@ -0,0 +1,54 @@
+from django.test import TestCase
+
+from django.contrib import admin
+
+from models import Person, Place
+
+class NameAdmin(admin.ModelAdmin):
+ list_display = ['name']
+ save_on_top = True
+
+class TestRegistration(TestCase):
+ def setUp(self):
+ self.site = admin.AdminSite()
+
+ def test_bare_registration(self):
+ self.site.register(Person)
+ self.assertTrue(
+ isinstance(self.site._registry[Person], admin.options.ModelAdmin)
+ )
+
+ def test_registration_with_model_admin(self):
+ self.site.register(Person, NameAdmin)
+ self.assertTrue(
+ isinstance(self.site._registry[Person], NameAdmin)
+ )
+
+ def test_prevent_double_registration(self):
+ self.site.register(Person)
+ self.assertRaises(admin.sites.AlreadyRegistered,
+ self.site.register,
+ Person)
+
+ def test_registration_with_star_star_options(self):
+ self.site.register(Person, search_fields=['name'])
+ self.assertEqual(self.site._registry[Person].search_fields, ['name'])
+
+ def test_star_star_overrides(self):
+ self.site.register(Person, NameAdmin,
+ search_fields=["name"], list_display=['__str__'])
+ self.assertEqual(self.site._registry[Person].search_fields, ['name'])
+ self.assertEqual(self.site._registry[Person].list_display,
+ ['action_checkbox', '__str__'])
+ self.assertTrue(self.site._registry[Person].save_on_top)
+
+ def test_iterable_registration(self):
+ self.site.register([Person, Place], search_fields=['name'])
+ self.assertTrue(
+ isinstance(self.site._registry[Person], admin.options.ModelAdmin)
+ )
+ self.assertEqual(self.site._registry[Person].search_fields, ['name'])
+ self.assertTrue(
+ isinstance(self.site._registry[Place], admin.options.ModelAdmin)
+ )
+ self.assertEqual(self.site._registry[Place].search_fields, ['name'])
diff --git a/parts/django/tests/regressiontests/admin_scripts/__init__.py b/parts/django/tests/regressiontests/admin_scripts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/app_with_import/__init__.py b/parts/django/tests/regressiontests/admin_scripts/app_with_import/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/app_with_import/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/app_with_import/models.py b/parts/django/tests/regressiontests/admin_scripts/app_with_import/models.py
new file mode 100644
index 0000000..06ca0e8
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/app_with_import/models.py
@@ -0,0 +1,7 @@
+from django.contrib.comments.models import Comment
+from django.db import models
+
+# Regression for #13368. This is an example of a model
+# that imports a class that has an abstract base class.
+class CommentScore(models.Model):
+ comment = models.OneToOneField(Comment, primary_key=True)
diff --git a/parts/django/tests/regressiontests/admin_scripts/broken_app/__init__.py b/parts/django/tests/regressiontests/admin_scripts/broken_app/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/broken_app/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/broken_app/models.py b/parts/django/tests/regressiontests/admin_scripts/broken_app/models.py
new file mode 100644
index 0000000..f37f1ef
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/broken_app/models.py
@@ -0,0 +1 @@
+from django.db import modelz
diff --git a/parts/django/tests/regressiontests/admin_scripts/complex_app/__init__.py b/parts/django/tests/regressiontests/admin_scripts/complex_app/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/complex_app/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/complex_app/admin/__init__.py b/parts/django/tests/regressiontests/admin_scripts/complex_app/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/complex_app/admin/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/complex_app/admin/foo.py b/parts/django/tests/regressiontests/admin_scripts/complex_app/admin/foo.py
new file mode 100644
index 0000000..1b933d0
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/complex_app/admin/foo.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+from admin_scripts.complex_app.models.foo import Foo
+admin.site.register(Foo)
diff --git a/parts/django/tests/regressiontests/admin_scripts/complex_app/models/__init__.py b/parts/django/tests/regressiontests/admin_scripts/complex_app/models/__init__.py
new file mode 100644
index 0000000..a4d6d95
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/complex_app/models/__init__.py
@@ -0,0 +1,4 @@
+from admin_scripts.complex_app.models.bar import Bar
+from admin_scripts.complex_app.models.foo import Foo
+
+__all__ = ['Foo', 'Bar']
diff --git a/parts/django/tests/regressiontests/admin_scripts/complex_app/models/bar.py b/parts/django/tests/regressiontests/admin_scripts/complex_app/models/bar.py
new file mode 100644
index 0000000..a5d8853
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/complex_app/models/bar.py
@@ -0,0 +1,7 @@
+from django.db import models
+
+from admin_scripts.complex_app.admin import foo
+class Bar(models.Model):
+ name = models.CharField(max_length=5)
+ class Meta:
+ app_label = 'complex_app'
diff --git a/parts/django/tests/regressiontests/admin_scripts/complex_app/models/foo.py b/parts/django/tests/regressiontests/admin_scripts/complex_app/models/foo.py
new file mode 100644
index 0000000..70c285e
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/complex_app/models/foo.py
@@ -0,0 +1,6 @@
+from django.db import models
+
+class Foo(models.Model):
+ name = models.CharField(max_length=5)
+ class Meta:
+ app_label = 'complex_app'
diff --git a/parts/django/tests/regressiontests/admin_scripts/management/__init__.py b/parts/django/tests/regressiontests/admin_scripts/management/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/management/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/management/commands/__init__.py b/parts/django/tests/regressiontests/admin_scripts/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/management/commands/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/management/commands/app_command.py b/parts/django/tests/regressiontests/admin_scripts/management/commands/app_command.py
new file mode 100644
index 0000000..f72e912
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/management/commands/app_command.py
@@ -0,0 +1,10 @@
+from django.core.management.base import AppCommand
+
+class Command(AppCommand):
+ help = 'Test Application-based commands'
+ requires_model_validation = False
+ args = '[appname ...]'
+
+ def handle_app(self, app, **options):
+ print 'EXECUTE:AppCommand app=%s, options=%s' % (app, sorted(options.items()))
+
diff --git a/parts/django/tests/regressiontests/admin_scripts/management/commands/base_command.py b/parts/django/tests/regressiontests/admin_scripts/management/commands/base_command.py
new file mode 100644
index 0000000..438f703
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/management/commands/base_command.py
@@ -0,0 +1,15 @@
+from django.core.management.base import BaseCommand
+from optparse import make_option
+
+class Command(BaseCommand):
+ option_list = BaseCommand.option_list + (
+ make_option('--option_a','-a', action='store', dest='option_a', default='1'),
+ make_option('--option_b','-b', action='store', dest='option_b', default='2'),
+ make_option('--option_c','-c', action='store', dest='option_c', default='3'),
+ )
+ help = 'Test basic commands'
+ requires_model_validation = False
+ args = '[labels ...]'
+
+ def handle(self, *labels, **options):
+ print 'EXECUTE:BaseCommand labels=%s, options=%s' % (labels, sorted(options.items()))
diff --git a/parts/django/tests/regressiontests/admin_scripts/management/commands/label_command.py b/parts/django/tests/regressiontests/admin_scripts/management/commands/label_command.py
new file mode 100644
index 0000000..2b735c8
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/management/commands/label_command.py
@@ -0,0 +1,9 @@
+from django.core.management.base import LabelCommand
+
+class Command(LabelCommand):
+ help = "Test Label-based commands"
+ requires_model_validation = False
+ args = '<label>'
+
+ def handle_label(self, label, **options):
+ print 'EXECUTE:LabelCommand label=%s, options=%s' % (label, sorted(options.items()))
diff --git a/parts/django/tests/regressiontests/admin_scripts/management/commands/noargs_command.py b/parts/django/tests/regressiontests/admin_scripts/management/commands/noargs_command.py
new file mode 100644
index 0000000..683eb7a
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/management/commands/noargs_command.py
@@ -0,0 +1,9 @@
+from django.core.management.base import NoArgsCommand
+
+class Command(NoArgsCommand):
+ help = "Test No-args commands"
+ requires_model_validation = False
+
+
+ def handle_noargs(self, **options):
+ print 'EXECUTE:NoArgsCommand options=%s' % sorted(options.items())
diff --git a/parts/django/tests/regressiontests/admin_scripts/models.py b/parts/django/tests/regressiontests/admin_scripts/models.py
new file mode 100644
index 0000000..96a40d2
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/models.py
@@ -0,0 +1,12 @@
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __unicode__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('-pub_date', 'headline')
+ \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_scripts/simple_app/__init__.py b/parts/django/tests/regressiontests/admin_scripts/simple_app/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/simple_app/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_scripts/simple_app/models.py b/parts/django/tests/regressiontests/admin_scripts/simple_app/models.py
new file mode 100644
index 0000000..65b30ed
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/simple_app/models.py
@@ -0,0 +1 @@
+from admin_scripts.complex_app.models.bar import Bar
diff --git a/parts/django/tests/regressiontests/admin_scripts/tests.py b/parts/django/tests/regressiontests/admin_scripts/tests.py
new file mode 100644
index 0000000..2a7f302
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_scripts/tests.py
@@ -0,0 +1,1199 @@
+"""
+A series of tests to establish that the command-line managment tools work as
+advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODULE
+and default settings.py files.
+"""
+import os
+import unittest
+import shutil
+import sys
+import re
+
+from django import conf, bin, get_version
+from django.conf import settings
+
+
+class AdminScriptTestCase(unittest.TestCase):
+ def write_settings(self, filename, apps=None, is_dir=False, sdict=None):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ if is_dir:
+ settings_dir = os.path.join(test_dir,filename)
+ os.mkdir(settings_dir)
+ settings_file = open(os.path.join(settings_dir,'__init__.py'), 'w')
+ else:
+ settings_file = open(os.path.join(test_dir, filename), 'w')
+ settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n')
+ exports = [
+ 'DATABASES',
+ 'ROOT_URLCONF'
+ ]
+ for s in exports:
+ if hasattr(settings, s):
+ o = getattr(settings, s)
+ if not isinstance(o, dict):
+ o = "'%s'" % o
+ settings_file.write("%s = %s\n" % (s, o))
+
+ if apps is None:
+ apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts']
+
+ if apps:
+ settings_file.write("INSTALLED_APPS = %s\n" % apps)
+
+ if sdict:
+ for k, v in sdict.items():
+ settings_file.write("%s = %s\n" % (k, v))
+
+ settings_file.close()
+
+ def remove_settings(self, filename, is_dir=False):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ full_name = os.path.join(test_dir, filename)
+ if is_dir:
+ shutil.rmtree(full_name)
+ else:
+ os.remove(full_name)
+
+ # Also try to remove the compiled file; if it exists, it could
+ # mess up later tests that depend upon the .py file not existing
+ try:
+ if sys.platform.startswith('java'):
+ # Jython produces module$py.class files
+ os.remove(re.sub(r'\.py$', '$py.class', full_name))
+ else:
+ # CPython produces module.pyc files
+ os.remove(full_name + 'c')
+ except OSError:
+ pass
+
+ def _ext_backend_paths(self):
+ """
+ Returns the paths for any external backend packages.
+ """
+ paths = []
+ first_package_re = re.compile(r'(^[^\.]+)\.')
+ for backend in settings.DATABASES.values():
+ result = first_package_re.findall(backend['ENGINE'])
+ if result and result != 'django':
+ backend_pkg = __import__(result[0])
+ backend_dir = os.path.dirname(backend_pkg.__file__)
+ paths.append(os.path.dirname(backend_dir))
+ return paths
+
+ def run_test(self, script, args, settings_file=None, apps=None):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ project_dir = os.path.dirname(test_dir)
+ base_dir = os.path.dirname(project_dir)
+ ext_backend_base_dirs = self._ext_backend_paths()
+
+ # Remember the old environment
+ old_django_settings_module = os.environ.get('DJANGO_SETTINGS_MODULE', None)
+ if sys.platform.startswith('java'):
+ python_path_var_name = 'JYTHONPATH'
+ else:
+ python_path_var_name = 'PYTHONPATH'
+
+ old_python_path = os.environ.get(python_path_var_name, None)
+ old_cwd = os.getcwd()
+
+ # Set the test environment
+ if settings_file:
+ os.environ['DJANGO_SETTINGS_MODULE'] = settings_file
+ elif 'DJANGO_SETTINGS_MODULE' in os.environ:
+ del os.environ['DJANGO_SETTINGS_MODULE']
+ python_path = [test_dir, base_dir]
+ python_path.extend(ext_backend_base_dirs)
+ os.environ[python_path_var_name] = os.pathsep.join(python_path)
+
+ # Build the command line
+ executable = sys.executable
+ arg_string = ' '.join(['%s' % arg for arg in args])
+ if ' ' in executable:
+ cmd = '""%s" "%s" %s"' % (executable, script, arg_string)
+ else:
+ cmd = '%s "%s" %s' % (executable, script, arg_string)
+
+ # Move to the test directory and run
+ os.chdir(test_dir)
+ try:
+ from subprocess import Popen, PIPE
+ p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ stdin, stdout, stderr = (p.stdin, p.stdout, p.stderr)
+ p.wait()
+ except ImportError:
+ stdin, stdout, stderr = os.popen3(cmd)
+ out, err = stdout.read(), stderr.read()
+
+ # Restore the old environment
+ if old_django_settings_module:
+ os.environ['DJANGO_SETTINGS_MODULE'] = old_django_settings_module
+ if old_python_path:
+ os.environ[python_path_var_name] = old_python_path
+ # Move back to the old working directory
+ os.chdir(old_cwd)
+
+ return out, err
+
+ def run_django_admin(self, args, settings_file=None):
+ bin_dir = os.path.abspath(os.path.dirname(bin.__file__))
+ return self.run_test(os.path.join(bin_dir,'django-admin.py'), args, settings_file)
+
+ def run_manage(self, args, settings_file=None):
+ conf_dir = os.path.dirname(conf.__file__)
+ template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py')
+
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ test_manage_py = os.path.join(test_dir, 'manage.py')
+ shutil.copyfile(template_manage_py, test_manage_py)
+
+ stdout, stderr = self.run_test('./manage.py', args, settings_file)
+
+ # Cleanup - remove the generated manage.py script
+ os.remove(test_manage_py)
+
+ return stdout, stderr
+
+ def assertNoOutput(self, stream):
+ "Utility assertion: assert that the given stream is empty"
+ self.assertEquals(len(stream), 0, "Stream should be empty: actually contains '%s'" % stream)
+ def assertOutput(self, stream, msg):
+ "Utility assertion: assert that the given message exists in the output"
+ self.assertTrue(msg in stream, "'%s' does not match actual output text '%s'" % (msg, stream))
+
+##########################################################################
+# DJANGO ADMIN TESTS
+# This first series of test classes checks the environment processing
+# of the django-admin.py script
+##########################################################################
+
+
+class DjangoAdminNoSettings(AdminScriptTestCase):
+ "A series of tests for django-admin.py when there is no settings.py file."
+
+ def test_builtin_command(self):
+ "no settings: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_bad_settings(self):
+ "no settings: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "no settings: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+
+class DjangoAdminDefaultSettings(AdminScriptTestCase):
+ """A series of tests for django-admin.py when using a settings.py file that
+ contains the test application.
+ """
+ def setUp(self):
+ self.write_settings('settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_builtin_command(self):
+ "default: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_settings(self):
+ "default: django-admin builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "default: django-admin builtin commands succeed if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_bad_settings(self):
+ "default: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "default: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_custom_command(self):
+ "default: django-admin can't execute user commands if it isn't provided settings"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "default: django-admin can execute user commands if settings are provided as argument"
+ args = ['noargs_command', '--settings=settings']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "default: django-admin can execute user commands if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase):
+ """A series of tests for django-admin.py when using a settings.py file that
+ contains the test application specified using a full path.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', ['django.contrib.auth', 'django.contrib.contenttypes', 'regressiontests.admin_scripts'])
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_builtin_command(self):
+ "fulldefault: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_settings(self):
+ "fulldefault: django-admin builtin commands succeed if a settings file is provided"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "fulldefault: django-admin builtin commands succeed if the environment contains settings"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_bad_settings(self):
+ "fulldefault: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "fulldefault: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_custom_command(self):
+ "fulldefault: django-admin can't execute user commands unless settings are provided"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "fulldefault: django-admin can execute user commands if settings are provided as argument"
+ args = ['noargs_command', '--settings=settings']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "fulldefault: django-admin can execute user commands if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+class DjangoAdminMinimalSettings(AdminScriptTestCase):
+ """A series of tests for django-admin.py when using a settings.py file that
+ doesn't contain the test application.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_builtin_command(self):
+ "minimal: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_settings(self):
+ "minimal: django-admin builtin commands fail if settings are provided as argument"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found')
+
+ def test_builtin_with_environment(self):
+ "minimal: django-admin builtin commands fail if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found')
+
+ def test_builtin_with_bad_settings(self):
+ "minimal: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "minimal: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_custom_command(self):
+ "minimal: django-admin can't execute user commands unless settings are provided"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "minimal: django-admin can't execute user commands, even if settings are provided as argument"
+ args = ['noargs_command', '--settings=settings']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_environment(self):
+ "minimal: django-admin can't execute user commands, even if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+class DjangoAdminAlternateSettings(AdminScriptTestCase):
+ """A series of tests for django-admin.py when using a settings file
+ with a name other than 'settings.py'.
+ """
+ def setUp(self):
+ self.write_settings('alternate_settings.py')
+
+ def tearDown(self):
+ self.remove_settings('alternate_settings.py')
+
+ def test_builtin_command(self):
+ "alternate: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_settings(self):
+ "alternate: django-admin builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=alternate_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "alternate: django-admin builtin commands succeed if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'alternate_settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_bad_settings(self):
+ "alternate: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "alternate: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_custom_command(self):
+ "alternate: django-admin can't execute user commands unless settings are provided"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "alternate: django-admin can execute user commands if settings are provided as argument"
+ args = ['noargs_command', '--settings=alternate_settings']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "alternate: django-admin can execute user commands if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args,'alternate_settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+
+class DjangoAdminMultipleSettings(AdminScriptTestCase):
+ """A series of tests for django-admin.py when multiple settings files
+ (including the default 'settings.py') are available. The default settings
+ file is insufficient for performing the operations described, so the
+ alternate settings must be used by the running script.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
+ self.write_settings('alternate_settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+ self.remove_settings('alternate_settings.py')
+
+ def test_builtin_command(self):
+ "alternate: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_settings(self):
+ "alternate: django-admin builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=alternate_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "alternate: django-admin builtin commands succeed if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'alternate_settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_bad_settings(self):
+ "alternate: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "alternate: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_custom_command(self):
+ "alternate: django-admin can't execute user commands unless settings are provided"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "alternate: django-admin can't execute user commands, even if settings are provided as argument"
+ args = ['noargs_command', '--settings=alternate_settings']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "alternate: django-admin can't execute user commands, even if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args,'alternate_settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+
+class DjangoAdminSettingsDirectory(AdminScriptTestCase):
+ """
+ A series of tests for django-admin.py when the settings file is in a
+ directory. (see #9751).
+ """
+
+ def setUp(self):
+ self.write_settings('settings', is_dir=True)
+
+ def tearDown(self):
+ self.remove_settings('settings', is_dir=True)
+
+ def test_setup_environ(self):
+ "directory: startapp creates the correct directory"
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ args = ['startapp','settings_test']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(err)
+ self.assert_(os.path.exists(os.path.join(test_dir, 'settings_test')))
+ shutil.rmtree(os.path.join(test_dir, 'settings_test'))
+
+ def test_builtin_command(self):
+ "directory: django-admin builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
+
+ def test_builtin_with_bad_settings(self):
+ "directory: django-admin builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "directory: django-admin builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_custom_command(self):
+ "directory: django-admin can't execute user commands unless settings are provided"
+ args = ['noargs_command']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_builtin_with_settings(self):
+ "directory: django-admin builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_django_admin(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "directory: django-admin builtin commands succeed if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_django_admin(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+
+##########################################################################
+# MANAGE.PY TESTS
+# This next series of test classes checks the environment processing
+# of the generated manage.py script
+##########################################################################
+
+class ManageNoSettings(AdminScriptTestCase):
+ "A series of tests for manage.py when there is no settings.py file."
+
+ def test_builtin_command(self):
+ "no settings: manage.py builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_builtin_with_bad_settings(self):
+ "no settings: manage.py builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_builtin_with_bad_environment(self):
+ "no settings: manage.py builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+
+class ManageDefaultSettings(AdminScriptTestCase):
+ """A series of tests for manage.py when using a settings.py file that
+ contains the test application.
+ """
+ def setUp(self):
+ self.write_settings('settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_builtin_command(self):
+ "default: manage.py builtin commands succeed when default settings are appropriate"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_settings(self):
+ "default: manage.py builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "default: manage.py builtin commands succeed if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_bad_settings(self):
+ "default: manage.py builtin commands succeed if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "default: manage.py builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'bad_settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_custom_command(self):
+ "default: manage.py can execute user commands when default settings are appropriate"
+ args = ['noargs_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_settings(self):
+ "default: manage.py can execute user commands when settings are provided as argument"
+ args = ['noargs_command', '--settings=settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "default: manage.py can execute user commands when settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_manage(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+
+class ManageFullPathDefaultSettings(AdminScriptTestCase):
+ """A series of tests for manage.py when using a settings.py file that
+ contains the test application specified using a full path.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', ['django.contrib.auth', 'django.contrib.contenttypes', 'regressiontests.admin_scripts'])
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_builtin_command(self):
+ "fulldefault: manage.py builtin commands succeed when default settings are appropriate"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_settings(self):
+ "fulldefault: manage.py builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "fulldefault: manage.py builtin commands succeed if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_bad_settings(self):
+ "fulldefault: manage.py builtin commands succeed if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "fulldefault: manage.py builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'bad_settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_custom_command(self):
+ "fulldefault: manage.py can execute user commands when default settings are appropriate"
+ args = ['noargs_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_settings(self):
+ "fulldefault: manage.py can execute user commands when settings are provided as argument"
+ args = ['noargs_command', '--settings=settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "fulldefault: manage.py can execute user commands when settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_manage(args,'settings')
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+class ManageMinimalSettings(AdminScriptTestCase):
+ """A series of tests for manage.py when using a settings.py file that
+ doesn't contain the test application.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_builtin_command(self):
+ "minimal: manage.py builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found')
+
+ def test_builtin_with_settings(self):
+ "minimal: manage.py builtin commands fail if settings are provided as argument"
+ args = ['sqlall','--settings=settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found')
+
+ def test_builtin_with_environment(self):
+ "minimal: manage.py builtin commands fail if settings are provided in the environment"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found')
+
+ def test_builtin_with_bad_settings(self):
+ "minimal: manage.py builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "minimal: manage.py builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found')
+
+ def test_custom_command(self):
+ "minimal: manage.py can't execute user commands without appropriate settings"
+ args = ['noargs_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "minimal: manage.py can't execute user commands, even if settings are provided as argument"
+ args = ['noargs_command', '--settings=settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_environment(self):
+ "minimal: manage.py can't execute user commands, even if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_manage(args,'settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+class ManageAlternateSettings(AdminScriptTestCase):
+ """A series of tests for manage.py when using a settings file
+ with a name other than 'settings.py'.
+ """
+ def setUp(self):
+ self.write_settings('alternate_settings.py')
+
+ def tearDown(self):
+ self.remove_settings('alternate_settings.py')
+
+ def test_builtin_command(self):
+ "alternate: manage.py builtin commands fail with an import error when no default settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_builtin_with_settings(self):
+ "alternate: manage.py builtin commands fail if settings are provided as argument but no defaults"
+ args = ['sqlall','--settings=alternate_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_builtin_with_environment(self):
+ "alternate: manage.py builtin commands fail if settings are provided in the environment but no defaults"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'alternate_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_builtin_with_bad_settings(self):
+ "alternate: manage.py builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_builtin_with_bad_environment(self):
+ "alternate: manage.py builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_custom_command(self):
+ "alternate: manage.py can't execute user commands"
+ args = ['noargs_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_custom_command_with_settings(self):
+ "alternate: manage.py can't execute user commands, even if settings are provided as argument"
+ args = ['noargs_command', '--settings=alternate_settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+ def test_custom_command_with_environment(self):
+ "alternate: manage.py can't execute user commands, even if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_manage(args,'alternate_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
+
+
+class ManageMultipleSettings(AdminScriptTestCase):
+ """A series of tests for manage.py when multiple settings files
+ (including the default 'settings.py') are available. The default settings
+ file is insufficient for performing the operations described, so the
+ alternate settings must be used by the running script.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
+ self.write_settings('alternate_settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+ self.remove_settings('alternate_settings.py')
+
+ def test_builtin_command(self):
+ "multiple: manage.py builtin commands fail with an import error when no settings provided"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found.')
+
+ def test_builtin_with_settings(self):
+ "multiple: manage.py builtin commands succeed if settings are provided as argument"
+ args = ['sqlall','--settings=alternate_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, 'CREATE TABLE')
+
+ def test_builtin_with_environment(self):
+ "multiple: manage.py builtin commands fail if settings are provided in the environment"
+ # FIXME: This doesn't seem to be the correct output.
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'alternate_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'App with label admin_scripts could not be found.')
+
+ def test_builtin_with_bad_settings(self):
+ "multiple: manage.py builtin commands fail if settings file (from argument) doesn't exist"
+ args = ['sqlall','--settings=bad_settings', 'admin_scripts']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Could not import settings 'bad_settings'")
+
+ def test_builtin_with_bad_environment(self):
+ "multiple: manage.py builtin commands fail if settings file (from environment) doesn't exist"
+ args = ['sqlall','admin_scripts']
+ out, err = self.run_manage(args,'bad_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "App with label admin_scripts could not be found")
+
+ def test_custom_command(self):
+ "multiple: manage.py can't execute user commands using default settings"
+ args = ['noargs_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+ def test_custom_command_with_settings(self):
+ "multiple: manage.py can execute user commands if settings are provided as argument"
+ args = ['noargs_command', '--settings=alternate_settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand")
+
+ def test_custom_command_with_environment(self):
+ "multiple: manage.py can execute user commands if settings are provided in environment"
+ args = ['noargs_command']
+ out, err = self.run_manage(args,'alternate_settings')
+ self.assertNoOutput(out)
+ self.assertOutput(err, "Unknown command: 'noargs_command'")
+
+
+class ManageValidate(AdminScriptTestCase):
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_nonexistent_app(self):
+ "manage.py validate reports an error on a non-existent app in INSTALLED_APPS"
+ self.write_settings('settings.py', apps=['admin_scriptz.broken_app'], sdict={'USE_I18N': False})
+ args = ['validate']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'No module named admin_scriptz')
+
+ def test_broken_app(self):
+ "manage.py validate reports an ImportError if an app's models.py raises one on import"
+ self.write_settings('settings.py', apps=['admin_scripts.broken_app'])
+ args = ['validate']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(out)
+ self.assertOutput(err, 'ImportError')
+
+ def test_complex_app(self):
+ "manage.py validate does not raise an ImportError validating a complex app with nested calls to load_app"
+ self.write_settings('settings.py',
+ apps=['admin_scripts.complex_app', 'admin_scripts.simple_app'],
+ sdict={'DEBUG': True})
+ args = ['validate']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, '0 errors found')
+
+ def test_app_with_import(self):
+ "manage.py validate does not raise errors when an app imports a base class that itself has an abstract base"
+ self.write_settings('settings.py',
+ apps=['admin_scripts.app_with_import', 'django.contrib.comments'],
+ sdict={'DEBUG': True})
+ args = ['validate']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, '0 errors found')
+
+##########################################################################
+# COMMAND PROCESSING TESTS
+# Check that user-space commands are correctly handled - in particular,
+# that arguments to the commands are correctly parsed and processed.
+##########################################################################
+
+class CommandTypes(AdminScriptTestCase):
+ "Tests for the various types of base command types that can be defined."
+ def setUp(self):
+ self.write_settings('settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_version(self):
+ "--version is handled as a special case"
+ args = ['--version']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ # Only check the first part of the version number
+ self.assertOutput(out, get_version().split('-')[0])
+
+ def test_help(self):
+ "--help is handled as a special case"
+ args = ['--help']
+ out, err = self.run_manage(args)
+ if sys.version_info < (2, 5):
+ self.assertOutput(out, "usage: manage.py subcommand [options] [args]")
+ else:
+ self.assertOutput(out, "Usage: manage.py subcommand [options] [args]")
+ self.assertOutput(err, "Type 'manage.py help <subcommand>' for help on a specific subcommand.")
+
+ def test_specific_help(self):
+ "--help can be used on a specific command"
+ args = ['sqlall','--help']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s).")
+
+ def test_base_command(self):
+ "User BaseCommands can execute when a label is provided"
+ args = ['base_command','testlabel']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', '1'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_base_command_no_label(self):
+ "User BaseCommands can execute when no labels are provided"
+ args = ['base_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=(), options=[('option_a', '1'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_base_command_multiple_label(self):
+ "User BaseCommands can execute when no labels are provided"
+ args = ['base_command','testlabel','anotherlabel']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel', 'anotherlabel'), options=[('option_a', '1'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_base_command_with_option(self):
+ "User BaseCommands can execute with options when a label is provided"
+ args = ['base_command','testlabel','--option_a=x']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_base_command_with_options(self):
+ "User BaseCommands can execute with multiple options when a label is provided"
+ args = ['base_command','testlabel','-a','x','--option_b=y']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', 'y'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_noargs(self):
+ "NoArg Commands can be executed"
+ args = ['noargs_command']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:NoArgsCommand options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_noargs_with_args(self):
+ "NoArg Commands raise an error if an argument is provided"
+ args = ['noargs_command','argument']
+ out, err = self.run_manage(args)
+ self.assertOutput(err, "Error: Command doesn't accept any arguments")
+
+ def test_app_command(self):
+ "User AppCommands can execute when a single app name is provided"
+ args = ['app_command', 'auth']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:AppCommand app=<module 'django.contrib.auth.models'")
+ self.assertOutput(out, os.sep.join(['django','contrib','auth','models.py']))
+ self.assertOutput(out, "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_app_command_no_apps(self):
+ "User AppCommands raise an error when no app name is provided"
+ args = ['app_command']
+ out, err = self.run_manage(args)
+ self.assertOutput(err, 'Error: Enter at least one appname.')
+
+ def test_app_command_multiple_apps(self):
+ "User AppCommands raise an error when multiple app names are provided"
+ args = ['app_command','auth','contenttypes']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:AppCommand app=<module 'django.contrib.auth.models'")
+ self.assertOutput(out, os.sep.join(['django','contrib','auth','models.py']))
+ self.assertOutput(out, "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+ self.assertOutput(out, "EXECUTE:AppCommand app=<module 'django.contrib.contenttypes.models'")
+ self.assertOutput(out, os.sep.join(['django','contrib','contenttypes','models.py']))
+ self.assertOutput(out, "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_app_command_invalid_appname(self):
+ "User AppCommands can execute when a single app name is provided"
+ args = ['app_command', 'NOT_AN_APP']
+ out, err = self.run_manage(args)
+ self.assertOutput(err, "App with label NOT_AN_APP could not be found")
+
+ def test_app_command_some_invalid_appnames(self):
+ "User AppCommands can execute when some of the provided app names are invalid"
+ args = ['app_command', 'auth', 'NOT_AN_APP']
+ out, err = self.run_manage(args)
+ self.assertOutput(err, "App with label NOT_AN_APP could not be found")
+
+ def test_label_command(self):
+ "User LabelCommands can execute when a label is provided"
+ args = ['label_command','testlabel']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+ def test_label_command_no_label(self):
+ "User LabelCommands raise an error if no label is provided"
+ args = ['label_command']
+ out, err = self.run_manage(args)
+ self.assertOutput(err, 'Enter at least one label')
+
+ def test_label_command_multiple_label(self):
+ "User LabelCommands are executed multiple times if multiple labels are provided"
+ args = ['label_command','testlabel','anotherlabel']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+ self.assertOutput(out, "EXECUTE:LabelCommand label=anotherlabel, options=[('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', '1')]")
+
+class ArgumentOrder(AdminScriptTestCase):
+ """Tests for 2-stage argument parsing scheme.
+
+ django-admin command arguments are parsed in 2 parts; the core arguments
+ (--settings, --traceback and --pythonpath) are parsed using a Lax parser.
+ This Lax parser ignores any unknown options. Then the full settings are
+ passed to the command parser, which extracts commands of interest to the
+ individual command.
+ """
+ def setUp(self):
+ self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
+ self.write_settings('alternate_settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+ self.remove_settings('alternate_settings.py')
+
+ def test_setting_then_option(self):
+ "Options passed after settings are correctly handled"
+ args = ['base_command','testlabel','--settings=alternate_settings','--option_a=x']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]")
+
+ def test_setting_then_short_option(self):
+ "Short options passed after settings are correctly handled"
+ args = ['base_command','testlabel','--settings=alternate_settings','--option_a=x']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]")
+
+ def test_option_then_setting(self):
+ "Options passed before settings are correctly handled"
+ args = ['base_command','testlabel','--option_a=x','--settings=alternate_settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]")
+
+ def test_short_option_then_setting(self):
+ "Short options passed before settings are correctly handled"
+ args = ['base_command','testlabel','-a','x','--settings=alternate_settings']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]")
+
+ def test_option_then_setting_then_option(self):
+ "Options are correctly handled when they are passed before and after a setting"
+ args = ['base_command','testlabel','--option_a=x','--settings=alternate_settings','--option_b=y']
+ out, err = self.run_manage(args)
+ self.assertNoOutput(err)
+ self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', 'y'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]")
diff --git a/parts/django/tests/regressiontests/admin_util/__init__.py b/parts/django/tests/regressiontests/admin_util/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_util/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_util/models.py b/parts/django/tests/regressiontests/admin_util/models.py
new file mode 100644
index 0000000..3191a55
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_util/models.py
@@ -0,0 +1,33 @@
+from django.db import models
+
+class Article(models.Model):
+ """
+ A simple Article model for testing
+ """
+ site = models.ForeignKey('sites.Site', related_name="admin_articles")
+ title = models.CharField(max_length=100)
+ title2 = models.CharField(max_length=100, verbose_name="another name")
+ created = models.DateTimeField()
+
+ def test_from_model(self):
+ return "nothing"
+
+ def test_from_model_with_override(self):
+ return "nothing"
+ test_from_model_with_override.short_description = "not What you Expect"
+
+class Count(models.Model):
+ num = models.PositiveSmallIntegerField()
+
+class Event(models.Model):
+ date = models.DateTimeField(auto_now_add=True)
+
+class Location(models.Model):
+ event = models.OneToOneField(Event, verbose_name='awesome event')
+
+class Guest(models.Model):
+ event = models.OneToOneField(Event)
+ name = models.CharField(max_length=255)
+
+ class Meta:
+ verbose_name = "awesome guest"
diff --git a/parts/django/tests/regressiontests/admin_util/tests.py b/parts/django/tests/regressiontests/admin_util/tests.py
new file mode 100644
index 0000000..7476d10
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_util/tests.py
@@ -0,0 +1,239 @@
+from datetime import datetime
+import unittest
+
+from django.conf import settings
+from django.db import models
+from django.utils.formats import localize
+from django.test import TestCase
+
+from django.contrib import admin
+from django.contrib.admin.util import display_for_field, label_for_field, lookup_field
+from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
+from django.contrib.sites.models import Site
+from django.contrib.admin.util import NestedObjects
+
+from models import Article, Count, Event, Location
+
+
+class NestedObjectsTests(TestCase):
+ """
+ Tests for ``NestedObject`` utility collection.
+
+ """
+ def setUp(self):
+ self.n = NestedObjects()
+ self.objs = [Count.objects.create(num=i) for i in range(5)]
+
+ def _check(self, target):
+ self.assertEquals(self.n.nested(lambda obj: obj.num), target)
+
+ def _add(self, obj, parent=None):
+ # don't bother providing the extra args that NestedObjects ignores
+ self.n.add(None, None, obj, None, parent)
+
+ def test_unrelated_roots(self):
+ self._add(self.objs[0])
+ self._add(self.objs[1])
+ self._add(self.objs[2], self.objs[1])
+
+ self._check([0, 1, [2]])
+
+ def test_siblings(self):
+ self._add(self.objs[0])
+ self._add(self.objs[1], self.objs[0])
+ self._add(self.objs[2], self.objs[0])
+
+ self._check([0, [1, 2]])
+
+ def test_duplicate_instances(self):
+ self._add(self.objs[0])
+ self._add(self.objs[1])
+ dupe = Count.objects.get(num=1)
+ self._add(dupe, self.objs[0])
+
+ self._check([0, 1])
+
+ def test_non_added_parent(self):
+ self._add(self.objs[0], self.objs[1])
+
+ self._check([0])
+
+ def test_cyclic(self):
+ self._add(self.objs[0], self.objs[2])
+ self._add(self.objs[1], self.objs[0])
+ self._add(self.objs[2], self.objs[1])
+ self._add(self.objs[0], self.objs[2])
+
+ self._check([0, [1, [2]]])
+
+
+class UtilTests(unittest.TestCase):
+ def test_values_from_lookup_field(self):
+ """
+ Regression test for #12654: lookup_field
+ """
+ SITE_NAME = 'example.com'
+ TITLE_TEXT = 'Some title'
+ CREATED_DATE = datetime.min
+ ADMIN_METHOD = 'admin method'
+ SIMPLE_FUNCTION = 'function'
+ INSTANCE_ATTRIBUTE = 'attr'
+
+ class MockModelAdmin(object):
+ def get_admin_value(self, obj):
+ return ADMIN_METHOD
+
+ simple_function = lambda obj: SIMPLE_FUNCTION
+
+ article = Article(
+ site=Site(domain=SITE_NAME),
+ title=TITLE_TEXT,
+ created=CREATED_DATE,
+ )
+ article.non_field = INSTANCE_ATTRIBUTE
+
+ verifications = (
+ ('site', SITE_NAME),
+ ('created', localize(CREATED_DATE)),
+ ('title', TITLE_TEXT),
+ ('get_admin_value', ADMIN_METHOD),
+ (simple_function, SIMPLE_FUNCTION),
+ ('test_from_model', article.test_from_model()),
+ ('non_field', INSTANCE_ATTRIBUTE)
+ )
+
+ mock_admin = MockModelAdmin()
+ for name, value in verifications:
+ field, attr, resolved_value = lookup_field(name, article, mock_admin)
+
+ if field is not None:
+ resolved_value = display_for_field(resolved_value, field)
+
+ self.assertEqual(value, resolved_value)
+
+ def test_null_display_for_field(self):
+ """
+ Regression test for #12550: display_for_field should handle None
+ value.
+ """
+ display_value = display_for_field(None, models.CharField())
+ self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
+
+ display_value = display_for_field(None, models.CharField(
+ choices=(
+ (None, "test_none"),
+ )
+ ))
+ self.assertEqual(display_value, "test_none")
+
+ display_value = display_for_field(None, models.DateField())
+ self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
+
+ display_value = display_for_field(None, models.TimeField())
+ self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
+
+ # Regression test for #13071: NullBooleanField has special
+ # handling.
+ display_value = display_for_field(None, models.NullBooleanField())
+ expected = u'<img src="%simg/admin/icon-unknown.gif" alt="None" />' % settings.ADMIN_MEDIA_PREFIX
+ self.assertEqual(display_value, expected)
+
+ display_value = display_for_field(None, models.DecimalField())
+ self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
+
+ display_value = display_for_field(None, models.FloatField())
+ self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
+
+ def test_label_for_field(self):
+ """
+ Tests for label_for_field
+ """
+ self.assertEquals(
+ label_for_field("title", Article),
+ "title"
+ )
+ self.assertEquals(
+ label_for_field("title2", Article),
+ "another name"
+ )
+ self.assertEquals(
+ label_for_field("title2", Article, return_attr=True),
+ ("another name", None)
+ )
+
+ self.assertEquals(
+ label_for_field("__unicode__", Article),
+ "article"
+ )
+ self.assertEquals(
+ label_for_field("__str__", Article),
+ "article"
+ )
+
+ self.assertRaises(
+ AttributeError,
+ lambda: label_for_field("unknown", Article)
+ )
+
+ def test_callable(obj):
+ return "nothing"
+ self.assertEquals(
+ label_for_field(test_callable, Article),
+ "Test callable"
+ )
+ self.assertEquals(
+ label_for_field(test_callable, Article, return_attr=True),
+ ("Test callable", test_callable)
+ )
+
+ self.assertEquals(
+ label_for_field("test_from_model", Article),
+ "Test from model"
+ )
+ self.assertEquals(
+ label_for_field("test_from_model", Article, return_attr=True),
+ ("Test from model", Article.test_from_model)
+ )
+ self.assertEquals(
+ label_for_field("test_from_model_with_override", Article),
+ "not What you Expect"
+ )
+
+ self.assertEquals(
+ label_for_field(lambda x: "nothing", Article),
+ "--"
+ )
+
+ class MockModelAdmin(object):
+ def test_from_model(self, obj):
+ return "nothing"
+ test_from_model.short_description = "not Really the Model"
+
+ self.assertEquals(
+ label_for_field("test_from_model", Article, model_admin=MockModelAdmin),
+ "not Really the Model"
+ )
+ self.assertEquals(
+ label_for_field("test_from_model", Article,
+ model_admin = MockModelAdmin,
+ return_attr = True
+ ),
+ ("not Really the Model", MockModelAdmin.test_from_model)
+ )
+
+ def test_related_name(self):
+ """
+ Regression test for #13963
+ """
+ self.assertEquals(
+ label_for_field('location', Event, return_attr=True),
+ ('location', None),
+ )
+ self.assertEquals(
+ label_for_field('event', Location, return_attr=True),
+ ('awesome event', None),
+ )
+ self.assertEquals(
+ label_for_field('guest', Event, return_attr=True),
+ ('awesome guest', None),
+ )
diff --git a/parts/django/tests/regressiontests/admin_validation/__init__.py b/parts/django/tests/regressiontests/admin_validation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_validation/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_validation/models.py b/parts/django/tests/regressiontests/admin_validation/models.py
new file mode 100644
index 0000000..24387cc
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_validation/models.py
@@ -0,0 +1,47 @@
+"""
+Tests of ModelAdmin validation logic.
+"""
+
+from django.db import models
+
+
+class Album(models.Model):
+ title = models.CharField(max_length=150)
+
+
+class Song(models.Model):
+ title = models.CharField(max_length=150)
+ album = models.ForeignKey(Album)
+ original_release = models.DateField(editable=False)
+
+ class Meta:
+ ordering = ('title',)
+
+ def __unicode__(self):
+ return self.title
+
+ def readonly_method_on_model(self):
+ # does nothing
+ pass
+
+
+class TwoAlbumFKAndAnE(models.Model):
+ album1 = models.ForeignKey(Album, related_name="album1_set")
+ album2 = models.ForeignKey(Album, related_name="album2_set")
+ e = models.CharField(max_length=1)
+
+
+class Author(models.Model):
+ name = models.CharField(max_length=100)
+
+
+class Book(models.Model):
+ name = models.CharField(max_length=100)
+ subtitle = models.CharField(max_length=100)
+ price = models.FloatField()
+ authors = models.ManyToManyField(Author, through='AuthorsBooks')
+
+
+class AuthorsBooks(models.Model):
+ author = models.ForeignKey(Author)
+ book = models.ForeignKey(Book)
diff --git a/parts/django/tests/regressiontests/admin_validation/tests.py b/parts/django/tests/regressiontests/admin_validation/tests.py
new file mode 100644
index 0000000..1872ca5
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_validation/tests.py
@@ -0,0 +1,247 @@
+from django.contrib import admin
+from django import forms
+from django.contrib.admin.validation import validate, validate_inline, \
+ ImproperlyConfigured
+from django.test import TestCase
+
+from models import Song, Book, Album, TwoAlbumFKAndAnE
+
+class SongForm(forms.ModelForm):
+ pass
+
+class ValidFields(admin.ModelAdmin):
+ form = SongForm
+ fields = ['title']
+
+class InvalidFields(admin.ModelAdmin):
+ form = SongForm
+ fields = ['spam']
+
+class ValidationTestCase(TestCase):
+ def assertRaisesMessage(self, exc, msg, func, *args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except Exception, e:
+ self.assertEqual(msg, str(e))
+ self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e)))
+
+ def test_readonly_and_editable(self):
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = ["original_release"]
+ fieldsets = [
+ (None, {
+ "fields": ["title", "original_release"],
+ }),
+ ]
+ validate(SongAdmin, Song)
+
+ def test_custom_modelforms_with_fields_fieldsets(self):
+ """
+ # Regression test for #8027: custom ModelForms with fields/fieldsets
+ """
+ validate(ValidFields, Song)
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "'InvalidFields.fields' refers to field 'spam' that is missing from the form.",
+ validate,
+ InvalidFields, Song)
+
+ def test_exclude_values(self):
+ """
+ Tests for basic validation of 'exclude' option values (#12689)
+ """
+ class ExcludedFields1(admin.ModelAdmin):
+ exclude = ('foo')
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "'ExcludedFields1.exclude' must be a list or tuple.",
+ validate,
+ ExcludedFields1, Book)
+
+ def test_exclude_duplicate_values(self):
+ class ExcludedFields2(admin.ModelAdmin):
+ exclude = ('name', 'name')
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "There are duplicate field(s) in ExcludedFields2.exclude",
+ validate,
+ ExcludedFields2, Book)
+
+ def test_exclude_in_inline(self):
+ class ExcludedFieldsInline(admin.TabularInline):
+ model = Song
+ exclude = ('foo')
+
+ class ExcludedFieldsAlbumAdmin(admin.ModelAdmin):
+ model = Album
+ inlines = [ExcludedFieldsInline]
+
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "'ExcludedFieldsInline.exclude' must be a list or tuple.",
+ validate,
+ ExcludedFieldsAlbumAdmin, Album)
+
+ def test_exclude_inline_model_admin(self):
+ """
+ # Regression test for #9932 - exclude in InlineModelAdmin
+ # should not contain the ForeignKey field used in ModelAdmin.model
+ """
+ class SongInline(admin.StackedInline):
+ model = Song
+ exclude = ['album']
+
+ class AlbumAdmin(admin.ModelAdmin):
+ model = Album
+ inlines = [SongInline]
+
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album.",
+ validate,
+ AlbumAdmin, Album)
+
+ def test_fk_exclusion(self):
+ """
+ Regression test for #11709 - when testing for fk excluding (when exclude is
+ given) make sure fk_name is honored or things blow up when there is more
+ than one fk to the parent model.
+ """
+ class TwoAlbumFKAndAnEInline(admin.TabularInline):
+ model = TwoAlbumFKAndAnE
+ exclude = ("e",)
+ fk_name = "album1"
+ validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+
+ def test_inline_self_validation(self):
+ class TwoAlbumFKAndAnEInline(admin.TabularInline):
+ model = TwoAlbumFKAndAnE
+
+ self.assertRaisesMessage(Exception,
+ "<class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'regressiontests.admin_validation.models.Album'>",
+ validate_inline,
+ TwoAlbumFKAndAnEInline, None, Album)
+
+ def test_inline_with_specified(self):
+ class TwoAlbumFKAndAnEInline(admin.TabularInline):
+ model = TwoAlbumFKAndAnE
+ fk_name = "album1"
+ validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+
+ def test_readonly(self):
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = ("title",)
+
+ validate(SongAdmin, Song)
+
+ def test_readonly_on_method(self):
+ def my_function(obj):
+ pass
+
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = (my_function,)
+
+ validate(SongAdmin, Song)
+
+ def test_readonly_on_modeladmin(self):
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = ("readonly_method_on_modeladmin",)
+
+ def readonly_method_on_modeladmin(self, obj):
+ pass
+
+ validate(SongAdmin, Song)
+
+ def test_readonly_method_on_model(self):
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = ("readonly_method_on_model",)
+
+ validate(SongAdmin, Song)
+
+ def test_nonexistant_field(self):
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = ("title", "nonexistant")
+
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.",
+ validate,
+ SongAdmin, Song)
+
+ def test_extra(self):
+ class SongAdmin(admin.ModelAdmin):
+ def awesome_song(self, instance):
+ if instance.title == "Born to Run":
+ return "Best Ever!"
+ return "Status unknown."
+ validate(SongAdmin, Song)
+
+ def test_readonly_lambda(self):
+ class SongAdmin(admin.ModelAdmin):
+ readonly_fields = (lambda obj: "test",)
+
+ validate(SongAdmin, Song)
+
+ def test_graceful_m2m_fail(self):
+ """
+ Regression test for #12203/#12237 - Fail more gracefully when a M2M field that
+ specifies the 'through' option is included in the 'fields' or the 'fieldsets'
+ ModelAdmin options.
+ """
+
+ class BookAdmin(admin.ModelAdmin):
+ fields = ['authors']
+
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
+ validate,
+ BookAdmin, Book)
+
+ def test_cannon_include_through(self):
+ class FieldsetBookAdmin(admin.ModelAdmin):
+ fieldsets = (
+ ('Header 1', {'fields': ('name',)}),
+ ('Header 2', {'fields': ('authors',)}),
+ )
+ self.assertRaisesMessage(ImproperlyConfigured,
+ "'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
+ validate,
+ FieldsetBookAdmin, Book)
+
+ def test_nested_fieldsets(self):
+ class NestedFieldsetAdmin(admin.ModelAdmin):
+ fieldsets = (
+ ('Main', {'fields': ('price', ('name', 'subtitle'))}),
+ )
+ validate(NestedFieldsetAdmin, Book)
+
+ def test_explicit_through_override(self):
+ """
+ Regression test for #12209 -- If the explicitly provided through model
+ is specified as a string, the admin should still be able use
+ Model.m2m_field.through
+ """
+
+ class AuthorsInline(admin.TabularInline):
+ model = Book.authors.through
+
+ class BookAdmin(admin.ModelAdmin):
+ inlines = [AuthorsInline]
+
+ # If the through model is still a string (and hasn't been resolved to a model)
+ # the validation will fail.
+ validate(BookAdmin, Book)
+
+ def test_non_model_fields(self):
+ """
+ Regression for ensuring ModelAdmin.fields can contain non-model fields
+ that broke with r11737
+ """
+ class SongForm(forms.ModelForm):
+ extra_data = forms.CharField()
+ class Meta:
+ model = Song
+
+ class FieldsOnFormOnlyAdmin(admin.ModelAdmin):
+ form = SongForm
+ fields = ['title', 'extra_data']
+
+ validate(FieldsOnFormOnlyAdmin, Song)
+
+
+
+
diff --git a/parts/django/tests/regressiontests/admin_views/__init__.py b/parts/django/tests/regressiontests/admin_views/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_views/customadmin.py b/parts/django/tests/regressiontests/admin_views/customadmin.py
new file mode 100644
index 0000000..34e39ef
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/customadmin.py
@@ -0,0 +1,34 @@
+"""
+A second, custom AdminSite -- see tests.CustomAdminSiteTests.
+"""
+from django.conf.urls.defaults import patterns
+from django.contrib import admin
+from django.http import HttpResponse
+
+import models
+
+class Admin2(admin.AdminSite):
+ login_template = 'custom_admin/login.html'
+ logout_template = 'custom_admin/logout.html'
+ index_template = 'custom_admin/index.html'
+ password_change_template = 'custom_admin/password_change_form.html'
+ password_change_done_template = 'custom_admin/password_change_done.html'
+
+ # A custom index view.
+ def index(self, request, extra_context=None):
+ return super(Admin2, self).index(request, {'foo': '*bar*'})
+
+ def get_urls(self):
+ return patterns('',
+ (r'^my_view/$', self.admin_view(self.my_view)),
+ ) + super(Admin2, self).get_urls()
+
+ def my_view(self, request):
+ return HttpResponse("Django is a magical pony!")
+
+site = Admin2(name="admin2")
+
+site.register(models.Article, models.ArticleAdmin)
+site.register(models.Section, inlines=[models.ArticleInline])
+site.register(models.Thing, models.ThingAdmin)
+site.register(models.Fabric, models.FabricAdmin)
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml
new file mode 100644
index 0000000..316e750
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="admin_views.subscriber">
+ <field type="CharField" name="name">John Doe</field>
+ <field type="CharField" name="email">john@example.org</field>
+ </object>
+ <object pk="2" model="admin_views.subscriber">
+ <field type="CharField" name="name">Max Mustermann</field>
+ <field type="CharField" name="email">max@example.org</field>
+ </object>
+ <object pk="1" model="admin_views.externalsubscriber">
+ <field type="CharField" name="name">John Doe</field>
+ <field type="CharField" name="email">john@example.org</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-colors.xml b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-colors.xml
new file mode 100644
index 0000000..e121356
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="admin_views.color">
+ <field type="CharField" name="value">Red</field>
+ <field type="BooleanField" name="warm">1</field>
+ </object>
+ <object pk="2" model="admin_views.color">
+ <field type="CharField" name="value">Orange</field>
+ <field type="BooleanField" name="warm">1</field>
+ </object>
+ <object pk="3" model="admin_views.color">
+ <field type="CharField" name="value">Blue</field>
+ <field type="BooleanField" name="warm">0</field>
+ </object>
+ <object pk="4" model="admin_views.color">
+ <field type="CharField" name="value">Green</field>
+ <field type="BooleanField" name="warm">0</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml
new file mode 100644
index 0000000..485bb27
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="admin_views.fabric">
+ <field type="CharField" name="surface">x</field>
+ </object>
+ <object pk="2" model="admin_views.fabric">
+ <field type="CharField" name="surface">y</field>
+ </object>
+ <object pk="3" model="admin_views.fabric">
+ <field type="CharField" name="surface">plain</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-person.xml b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-person.xml
new file mode 100644
index 0000000..ff00fd2
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-person.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="admin_views.person">
+ <field type="CharField" name="name">John Mauchly</field>
+ <field type="IntegerField" name="gender">1</field>
+ <field type="BooleanField" name="alive">True</field>
+ </object>
+ <object pk="2" model="admin_views.person">
+ <field type="CharField" name="name">Grace Hopper</field>
+ <field type="IntegerField" name="gender">1</field>
+ <field type="BooleanField" name="alive">False</field>
+ </object>
+ <object pk="3" model="admin_views.person">
+ <field type="CharField" name="name">Guido van Rossum</field>
+ <field type="IntegerField" name="gender">1</field>
+ <field type="BooleanField" name="alive">True</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml
new file mode 100644
index 0000000..5652aa1
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="1" model="admin_views.book">
+ <field type="CharField" name="name">Lærdommer</field>
+ </object>
+ <object pk="1" model="admin_views.promo">
+ <field type="CharField" name="name">&lt;Promo for Lærdommer&gt;</field>
+ <field to="admin_views.book" name="book" rel="ManyToOneRel">1</field>
+ </object>
+ <object pk="1" model="admin_views.chapter">
+ <field type="CharField" name="title">Norske bostaver æøå skaper problemer</field>
+ <field type="TextField" name="content">&lt;p&gt;Svært frustrerende med UnicodeDecodeErro&lt;/p&gt;</field>
+ <field to="admin_views.book" name="book" rel="ManyToOneRel">1</field>
+ </object>
+ <object pk="2" model="admin_views.chapter">
+ <field type="CharField" name="title">Kjærlighet</field>
+ <field type="TextField" name="content">&lt;p&gt;La kjærligheten til de lidende seire.&lt;/p&gt;</field>
+ <field to="admin_views.book" name="book" rel="ManyToOneRel">1</field>
+ </object>
+ <object pk="3" model="admin_views.chapter">
+ <field type="CharField" name="title">Kjærlighet</field>
+ <field type="TextField" name="content">&lt;p&gt;Noe innhold&lt;/p&gt;</field>
+ <field to="admin_views.book" name="book" rel="ManyToOneRel">1</field>
+ </object>
+ <object pk="1" model="admin_views.chapterxtra1">
+ <field type="CharField" name="xtra">&lt;Xtra(1) Norske bostaver æøå skaper problemer&gt;</field>
+ <field to="admin_views.chapter" name="chap" rel="OneToOneRel">1</field>
+ </object>
+ <object pk="2" model="admin_views.chapterxtra1">
+ <field type="CharField" name="xtra">&lt;Xtra(1) Kjærlighet&gt;</field>
+ <field to="admin_views.chapter" name="chap" rel="OneToOneRel">2</field>
+ </object>
+ <object pk="3" model="admin_views.chapterxtra1">
+ <field type="CharField" name="xtra">&lt;Xtra(1) Kjærlighet&gt;</field>
+ <field to="admin_views.chapter" name="chap" rel="OneToOneRel">3</field>
+ </object>
+ <object pk="1" model="admin_views.chapterxtra2">
+ <field type="CharField" name="xtra">&lt;Xtra(2) Norske bostaver æøå skaper problemer&gt;</field>
+ <field to="admin_views.chapter" name="chap" rel="OneToOneRel">1</field>
+ </object>
+ <object pk="2" model="admin_views.chapterxtra2">
+ <field type="CharField" name="xtra">&lt;Xtra(2) Kjærlighet&gt;</field>
+ <field to="admin_views.chapter" name="chap" rel="OneToOneRel">2</field>
+ </object>
+ <object pk="3" model="admin_views.chapterxtra2">
+ <field type="CharField" name="xtra">&lt;Xtra(2) Kjærlighet&gt;</field>
+ <field to="admin_views.chapter" name="chap" rel="OneToOneRel">3</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-users.xml b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-users.xml
new file mode 100644
index 0000000..f1ff296
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/admin-views-users.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="101" model="auth.user">
+ <field type="CharField" name="username">adduser</field>
+ <field type="CharField" name="first_name">Add</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">auser@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">False</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="102" model="auth.user">
+ <field type="CharField" name="username">changeuser</field>
+ <field type="CharField" name="first_name">Change</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">cuser@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">False</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="103" model="auth.user">
+ <field type="CharField" name="username">deleteuser</field>
+ <field type="CharField" name="first_name">Delete</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">duser@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">False</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="104" model="auth.user">
+ <field type="CharField" name="username">joepublic</field>
+ <field type="CharField" name="first_name">Joe</field>
+ <field type="CharField" name="last_name">Public</field>
+ <field type="CharField" name="email">joepublic@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">False</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">False</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="1" model="admin_views.section">
+ <field type="CharField" name="name">Test section</field>
+ </object>
+ <object pk="1" model="admin_views.article">
+ <field type="TextField" name="content">&lt;p&gt;Middle content&lt;/p&gt;</field>
+ <field type="DateTimeField" name="date">2008-03-18 11:54:58</field>
+ <field to="admin_views.section" name="section" rel="ManyToOneRel">1</field>
+ </object>
+ <object pk="2" model="admin_views.article">
+ <field type="TextField" name="content">&lt;p&gt;Oldest content&lt;/p&gt;</field>
+ <field type="DateTimeField" name="date">2000-03-18 11:54:58</field>
+ <field to="admin_views.section" name="section" rel="ManyToOneRel">1</field>
+ </object>
+ <object pk="3" model="admin_views.article">
+ <field type="TextField" name="content">&lt;p&gt;Newest content&lt;/p&gt;</field>
+ <field type="DateTimeField" name="date">2009-03-18 11:54:58</field>
+ <field to="admin_views.section" name="section" rel="ManyToOneRel">1</field>
+ </object>
+
+
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/deleted-objects.xml b/parts/django/tests/regressiontests/admin_views/fixtures/deleted-objects.xml
new file mode 100644
index 0000000..92e43db
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/deleted-objects.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="admin_views.villain">
+ <field type="CharField" name="name">Adam</field>
+ </object>
+ <object pk="2" model="admin_views.villain">
+ <field type="CharField" name="name">Sue</field>
+ </object>
+ <object pk="3" model="admin_views.villain">
+ <field type="CharField" name="name">Bob</field>
+ </object>
+ <object pk="3" model="admin_views.supervillain">
+ </object>
+ <object pk="1" model="admin_views.plot">
+ <field type="CharField" name="name">World Domination</field>
+ <field type="ForeignKey" name="team_leader">1</field>
+ <field type="ForeignKey" name="contact">2</field>
+ </object>
+ <object pk="2" model="admin_views.plot">
+ <field type="CharField" name="name">World Peace</field>
+ <field type="ForeignKey" name="team_leader">2</field>
+ <field type="ForeignKey" name="contact">2</field>
+ </object>
+ <object pk="1" model="admin_views.plotdetails">
+ <field type="CharField" name="details">almost finished</field>
+ <field type="ForeignKey" name="plot">1</field>
+ </object>
+ <object pk="1" model="admin_views.secrethideout">
+ <field type="CharField" name="location">underground bunker</field>
+ <field type="ForeignKey" name="villain">1</field>
+ </object>
+ <object pk="2" model="admin_views.secrethideout">
+ <field type="CharField" name="location">floating castle</field>
+ <field type="ForeignKey" name="villain">3</field>
+ </object>
+ <object pk="1" model="admin_views.supersecrethideout">
+ <field type="CharField" name="location">super floating castle!</field>
+ <field type="ForeignKey" name="supervillain">3</field>
+ </object>
+ <object pk="1" model="admin_views.cyclicone">
+ <field type="CharField" name="name">I am recursive</field>
+ <field type="ForeignKey" name="two">1</field>
+ </object>
+ <object pk="1" model="admin_views.cyclictwo">
+ <field type="CharField" name="name">I am recursive too</field>
+ <field type="ForeignKey" name="one">1</field>
+ </object>
+ <object pk="3" model="admin_views.plot">
+ <field type="CharField" name="name">Corn Conspiracy</field>
+ <field type="ForeignKey" name="team_leader">1</field>
+ <field type="ForeignKey" name="contact">1</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/multiple-child-classes.json b/parts/django/tests/regressiontests/admin_views/fixtures/multiple-child-classes.json
new file mode 100644
index 0000000..5cadf4c
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/multiple-child-classes.json
@@ -0,0 +1,107 @@
+[
+ {
+ "pk": 1,
+ "model": "admin_views.title",
+ "fields":
+ {
+ }
+ },
+
+ {
+ "pk": 2,
+ "model": "admin_views.title",
+ "fields":
+ {
+ }
+ },
+
+ {
+ "pk": 3,
+ "model": "admin_views.title",
+ "fields":
+ {
+ }
+ },
+
+ {
+ "pk": 4,
+ "model": "admin_views.title",
+ "fields":
+ {
+ }
+ },
+
+ {
+ "pk": 1,
+ "model": "admin_views.titletranslation",
+ "fields":
+ {
+ "text": "Bar",
+ "title": 1
+ }
+ },
+
+ {
+ "pk": 2,
+ "model": "admin_views.titletranslation",
+ "fields":
+ {
+ "text": "Foo",
+ "title": 2
+ }
+ },
+
+ {
+ "pk": 3,
+ "model": "admin_views.titletranslation",
+ "fields":
+ {
+ "text": "Few",
+ "title": 3
+ }
+ },
+
+ {
+ "pk": 4,
+ "model": "admin_views.titletranslation",
+ "fields":
+ {
+ "text": "Bas",
+ "title": 4
+ }
+ },
+
+ {
+ "pk": 1,
+ "model": "admin_views.recommender",
+ "fields":
+ {
+ }
+ },
+
+ {
+ "pk": 4,
+ "model": "admin_views.recommender",
+ "fields":
+ {
+ }
+ },
+
+ {
+ "pk": 2,
+ "model": "admin_views.recommendation",
+ "fields":
+ {
+ "recommender": 1
+ }
+ },
+
+ {
+ "pk": 3,
+ "model": "admin_views.recommendation",
+ "fields":
+ {
+ "recommender": 4
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_views/fixtures/string-primary-key.xml b/parts/django/tests/regressiontests/admin_views/fixtures/string-primary-key.xml
new file mode 100644
index 0000000..8e1dbf0
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/fixtures/string-primary-key.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="admin_views.modelwithstringprimarykey">
+ <field type="CharField" name="id"><![CDATA[abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`]]></field>
+ </object>
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_views/models.py b/parts/django/tests/regressiontests/admin_views/models.py
new file mode 100644
index 0000000..191b4f3
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/models.py
@@ -0,0 +1,636 @@
+# -*- coding: utf-8 -*-
+import datetime
+import tempfile
+import os
+
+from django.contrib import admin
+from django.core.files.storage import FileSystemStorage
+from django.contrib.admin.views.main import ChangeList
+from django.core.mail import EmailMessage
+from django.db import models
+from django import forms
+from django.forms.models import BaseModelFormSet
+from django.contrib.auth.models import User
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+
+class Section(models.Model):
+ """
+ A simple section that links to articles, to test linking to related items
+ in admin views.
+ """
+ name = models.CharField(max_length=100)
+
+class Article(models.Model):
+ """
+ A simple article to test admin views. Test backwards compatibility.
+ """
+ title = models.CharField(max_length=100)
+ content = models.TextField()
+ date = models.DateTimeField()
+ section = models.ForeignKey(Section, null=True, blank=True)
+
+ def __unicode__(self):
+ return self.title
+
+ def model_year(self):
+ return self.date.year
+ model_year.admin_order_field = 'date'
+ model_year.short_description = ''
+
+class Book(models.Model):
+ """
+ A simple book that has chapters.
+ """
+ name = models.CharField(max_length=100, verbose_name=u'¿Name?')
+
+ def __unicode__(self):
+ return self.name
+
+class Promo(models.Model):
+ name = models.CharField(max_length=100, verbose_name=u'¿Name?')
+ book = models.ForeignKey(Book)
+
+ def __unicode__(self):
+ return self.name
+
+class Chapter(models.Model):
+ title = models.CharField(max_length=100, verbose_name=u'¿Title?')
+ content = models.TextField()
+ book = models.ForeignKey(Book)
+
+ def __unicode__(self):
+ return self.title
+
+ class Meta:
+ # Use a utf-8 bytestring to ensure it works (see #11710)
+ verbose_name = '¿Chapter?'
+
+class ChapterXtra1(models.Model):
+ chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
+ xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
+
+ def __unicode__(self):
+ return u'¿Xtra1: %s' % self.xtra
+
+class ChapterXtra2(models.Model):
+ chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
+ xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
+
+ def __unicode__(self):
+ return u'¿Xtra2: %s' % self.xtra
+
+def callable_year(dt_value):
+ return dt_value.year
+callable_year.admin_order_field = 'date'
+
+class ArticleInline(admin.TabularInline):
+ model = Article
+
+class ChapterInline(admin.TabularInline):
+ model = Chapter
+
+class ArticleAdmin(admin.ModelAdmin):
+ list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year')
+ list_filter = ('date', 'section')
+
+ def changelist_view(self, request):
+ "Test that extra_context works"
+ return super(ArticleAdmin, self).changelist_view(
+ request, extra_context={
+ 'extra_var': 'Hello!'
+ }
+ )
+
+ def modeladmin_year(self, obj):
+ return obj.date.year
+ modeladmin_year.admin_order_field = 'date'
+ modeladmin_year.short_description = None
+
+class CustomArticle(models.Model):
+ content = models.TextField()
+ date = models.DateTimeField()
+
+class CustomArticleAdmin(admin.ModelAdmin):
+ """
+ Tests various hooks for using custom templates and contexts.
+ """
+ change_list_template = 'custom_admin/change_list.html'
+ change_form_template = 'custom_admin/change_form.html'
+ add_form_template = 'custom_admin/add_form.html'
+ object_history_template = 'custom_admin/object_history.html'
+ delete_confirmation_template = 'custom_admin/delete_confirmation.html'
+ delete_selected_confirmation_template = 'custom_admin/delete_selected_confirmation.html'
+
+ def changelist_view(self, request):
+ "Test that extra_context works"
+ return super(CustomArticleAdmin, self).changelist_view(
+ request, extra_context={
+ 'extra_var': 'Hello!'
+ }
+ )
+
+class ModelWithStringPrimaryKey(models.Model):
+ id = models.CharField(max_length=255, primary_key=True)
+
+ def __unicode__(self):
+ return self.id
+
+class Color(models.Model):
+ value = models.CharField(max_length=10)
+ warm = models.BooleanField()
+ def __unicode__(self):
+ return self.value
+
+class Thing(models.Model):
+ title = models.CharField(max_length=20)
+ color = models.ForeignKey(Color, limit_choices_to={'warm': True})
+ def __unicode__(self):
+ return self.title
+
+class ThingAdmin(admin.ModelAdmin):
+ list_filter = ('color',)
+
+class Fabric(models.Model):
+ NG_CHOICES = (
+ ('Textured', (
+ ('x', 'Horizontal'),
+ ('y', 'Vertical'),
+ )
+ ),
+ ('plain', 'Smooth'),
+ )
+ surface = models.CharField(max_length=20, choices=NG_CHOICES)
+
+class FabricAdmin(admin.ModelAdmin):
+ list_display = ('surface',)
+ list_filter = ('surface',)
+
+class Person(models.Model):
+ GENDER_CHOICES = (
+ (1, "Male"),
+ (2, "Female"),
+ )
+ name = models.CharField(max_length=100)
+ gender = models.IntegerField(choices=GENDER_CHOICES)
+ alive = models.BooleanField()
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ ordering = ["id"]
+
+class BasePersonModelFormSet(BaseModelFormSet):
+ def clean(self):
+ for person_dict in self.cleaned_data:
+ person = person_dict.get('id')
+ alive = person_dict.get('alive')
+ if person and alive and person.name == "Grace Hopper":
+ raise forms.ValidationError, "Grace is not a Zombie"
+
+class PersonAdmin(admin.ModelAdmin):
+ list_display = ('name', 'gender', 'alive')
+ list_editable = ('gender', 'alive')
+ list_filter = ('gender',)
+ search_fields = (u'name',)
+ ordering = ["id"]
+ save_as = True
+
+ def get_changelist_formset(self, request, **kwargs):
+ return super(PersonAdmin, self).get_changelist_formset(request,
+ formset=BasePersonModelFormSet, **kwargs)
+
+
+class Persona(models.Model):
+ """
+ A simple persona associated with accounts, to test inlining of related
+ accounts which inherit from a common accounts class.
+ """
+ name = models.CharField(blank=False, max_length=80)
+ def __unicode__(self):
+ return self.name
+
+class Account(models.Model):
+ """
+ A simple, generic account encapsulating the information shared by all
+ types of accounts.
+ """
+ username = models.CharField(blank=False, max_length=80)
+ persona = models.ForeignKey(Persona, related_name="accounts")
+ servicename = u'generic service'
+
+ def __unicode__(self):
+ return "%s: %s" % (self.servicename, self.username)
+
+class FooAccount(Account):
+ """A service-specific account of type Foo."""
+ servicename = u'foo'
+
+class BarAccount(Account):
+ """A service-specific account of type Bar."""
+ servicename = u'bar'
+
+class FooAccountAdmin(admin.StackedInline):
+ model = FooAccount
+ extra = 1
+
+class BarAccountAdmin(admin.StackedInline):
+ model = BarAccount
+ extra = 1
+
+class PersonaAdmin(admin.ModelAdmin):
+ inlines = (
+ FooAccountAdmin,
+ BarAccountAdmin
+ )
+
+class Subscriber(models.Model):
+ name = models.CharField(blank=False, max_length=80)
+ email = models.EmailField(blank=False, max_length=175)
+
+ def __unicode__(self):
+ return "%s (%s)" % (self.name, self.email)
+
+class SubscriberAdmin(admin.ModelAdmin):
+ actions = ['mail_admin']
+
+ def mail_admin(self, request, selected):
+ EmailMessage(
+ 'Greetings from a ModelAdmin action',
+ 'This is the test email from a admin action',
+ 'from@example.com',
+ ['to@example.com']
+ ).send()
+
+class ExternalSubscriber(Subscriber):
+ pass
+
+class OldSubscriber(Subscriber):
+ pass
+
+def external_mail(modeladmin, request, selected):
+ EmailMessage(
+ 'Greetings from a function action',
+ 'This is the test email from a function action',
+ 'from@example.com',
+ ['to@example.com']
+ ).send()
+
+def redirect_to(modeladmin, request, selected):
+ from django.http import HttpResponseRedirect
+ return HttpResponseRedirect('/some-where-else/')
+
+class ExternalSubscriberAdmin(admin.ModelAdmin):
+ actions = [external_mail, redirect_to]
+
+class Media(models.Model):
+ name = models.CharField(max_length=60)
+
+class Podcast(Media):
+ release_date = models.DateField()
+
+class PodcastAdmin(admin.ModelAdmin):
+ list_display = ('name', 'release_date')
+ list_editable = ('release_date',)
+
+ ordering = ('name',)
+
+class Vodcast(Media):
+ media = models.OneToOneField(Media, primary_key=True, parent_link=True)
+ released = models.BooleanField(default=False)
+
+class VodcastAdmin(admin.ModelAdmin):
+ list_display = ('name', 'released')
+ list_editable = ('released',)
+
+ ordering = ('name',)
+
+class Parent(models.Model):
+ name = models.CharField(max_length=128)
+
+class Child(models.Model):
+ parent = models.ForeignKey(Parent, editable=False)
+ name = models.CharField(max_length=30, blank=True)
+
+class ChildInline(admin.StackedInline):
+ model = Child
+
+class ParentAdmin(admin.ModelAdmin):
+ model = Parent
+ inlines = [ChildInline]
+
+class EmptyModel(models.Model):
+ def __unicode__(self):
+ return "Primary key = %s" % self.id
+
+class EmptyModelAdmin(admin.ModelAdmin):
+ def queryset(self, request):
+ return super(EmptyModelAdmin, self).queryset(request).filter(pk__gt=1)
+
+class OldSubscriberAdmin(admin.ModelAdmin):
+ actions = None
+
+temp_storage = FileSystemStorage(tempfile.mkdtemp())
+UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload')
+
+class Gallery(models.Model):
+ name = models.CharField(max_length=100)
+
+class Picture(models.Model):
+ name = models.CharField(max_length=100)
+ image = models.FileField(storage=temp_storage, upload_to='test_upload')
+ gallery = models.ForeignKey(Gallery, related_name="pictures")
+
+class PictureInline(admin.TabularInline):
+ model = Picture
+ extra = 1
+
+class GalleryAdmin(admin.ModelAdmin):
+ inlines = [PictureInline]
+
+class PictureAdmin(admin.ModelAdmin):
+ pass
+
+class Language(models.Model):
+ iso = models.CharField(max_length=5, primary_key=True)
+ name = models.CharField(max_length=50)
+ english_name = models.CharField(max_length=50)
+ shortlist = models.BooleanField(default=False)
+
+ class Meta:
+ ordering = ('iso',)
+
+class LanguageAdmin(admin.ModelAdmin):
+ list_display = ['iso', 'shortlist', 'english_name', 'name']
+ list_editable = ['shortlist']
+
+# a base class for Recommender and Recommendation
+class Title(models.Model):
+ pass
+
+class TitleTranslation(models.Model):
+ title = models.ForeignKey(Title)
+ text = models.CharField(max_length=100)
+
+class Recommender(Title):
+ pass
+
+class Recommendation(Title):
+ recommender = models.ForeignKey(Recommender)
+
+class RecommendationAdmin(admin.ModelAdmin):
+ search_fields = ('titletranslation__text', 'recommender__titletranslation__text',)
+
+class Collector(models.Model):
+ name = models.CharField(max_length=100)
+
+class Widget(models.Model):
+ owner = models.ForeignKey(Collector)
+ name = models.CharField(max_length=100)
+
+class DooHickey(models.Model):
+ code = models.CharField(max_length=10, primary_key=True)
+ owner = models.ForeignKey(Collector)
+ name = models.CharField(max_length=100)
+
+class Grommet(models.Model):
+ code = models.AutoField(primary_key=True)
+ owner = models.ForeignKey(Collector)
+ name = models.CharField(max_length=100)
+
+class Whatsit(models.Model):
+ index = models.IntegerField(primary_key=True)
+ owner = models.ForeignKey(Collector)
+ name = models.CharField(max_length=100)
+
+class Doodad(models.Model):
+ name = models.CharField(max_length=100)
+
+class FancyDoodad(Doodad):
+ owner = models.ForeignKey(Collector)
+ expensive = models.BooleanField(default=True)
+
+class WidgetInline(admin.StackedInline):
+ model = Widget
+
+class DooHickeyInline(admin.StackedInline):
+ model = DooHickey
+
+class GrommetInline(admin.StackedInline):
+ model = Grommet
+
+class WhatsitInline(admin.StackedInline):
+ model = Whatsit
+
+class FancyDoodadInline(admin.StackedInline):
+ model = FancyDoodad
+
+class Category(models.Model):
+ collector = models.ForeignKey(Collector)
+ order = models.PositiveIntegerField()
+
+ class Meta:
+ ordering = ('order',)
+
+ def __unicode__(self):
+ return u'%s:o%s' % (self.id, self.order)
+
+class CategoryAdmin(admin.ModelAdmin):
+ list_display = ('id', 'collector', 'order')
+ list_editable = ('order',)
+
+class CategoryInline(admin.StackedInline):
+ model = Category
+
+class CollectorAdmin(admin.ModelAdmin):
+ inlines = [
+ WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline,
+ FancyDoodadInline, CategoryInline
+ ]
+
+class Link(models.Model):
+ posted = models.DateField(
+ default=lambda: datetime.date.today() - datetime.timedelta(days=7)
+ )
+ url = models.URLField()
+ post = models.ForeignKey("Post")
+
+
+class LinkInline(admin.TabularInline):
+ model = Link
+ extra = 1
+
+ readonly_fields = ("posted",)
+
+
+class Post(models.Model):
+ title = models.CharField(max_length=100)
+ content = models.TextField()
+ posted = models.DateField(default=datetime.date.today)
+ public = models.NullBooleanField()
+
+ def awesomeness_level(self):
+ return "Very awesome."
+
+class PostAdmin(admin.ModelAdmin):
+ list_display = ['title', 'public']
+ readonly_fields = ('posted', 'awesomeness_level', 'coolness', 'value', lambda obj: "foo")
+
+ inlines = [
+ LinkInline
+ ]
+
+ def coolness(self, instance):
+ if instance.pk:
+ return "%d amount of cool." % instance.pk
+ else:
+ return "Unkown coolness."
+
+ def value(self, instance):
+ return 1000
+ value.short_description = 'Value in $US'
+
+class Gadget(models.Model):
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+class CustomChangeList(ChangeList):
+ def get_query_set(self):
+ return self.root_query_set.filter(pk=9999) # Does not exist
+
+class GadgetAdmin(admin.ModelAdmin):
+ def get_changelist(self, request, **kwargs):
+ return CustomChangeList
+
+class Villain(models.Model):
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+class SuperVillain(Villain):
+ pass
+
+class FunkyTag(models.Model):
+ "Because we all know there's only one real use case for GFKs."
+ name = models.CharField(max_length=25)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+
+ def __unicode__(self):
+ return self.name
+
+class Plot(models.Model):
+ name = models.CharField(max_length=100)
+ team_leader = models.ForeignKey(Villain, related_name='lead_plots')
+ contact = models.ForeignKey(Villain, related_name='contact_plots')
+ tags = generic.GenericRelation(FunkyTag)
+
+ def __unicode__(self):
+ return self.name
+
+class PlotDetails(models.Model):
+ details = models.CharField(max_length=100)
+ plot = models.OneToOneField(Plot)
+
+ def __unicode__(self):
+ return self.details
+
+class SecretHideout(models.Model):
+ """ Secret! Not registered with the admin! """
+ location = models.CharField(max_length=100)
+ villain = models.ForeignKey(Villain)
+
+ def __unicode__(self):
+ return self.location
+
+class SuperSecretHideout(models.Model):
+ """ Secret! Not registered with the admin! """
+ location = models.CharField(max_length=100)
+ supervillain = models.ForeignKey(SuperVillain)
+
+ def __unicode__(self):
+ return self.location
+
+class CyclicOne(models.Model):
+ name = models.CharField(max_length=25)
+ two = models.ForeignKey('CyclicTwo')
+
+ def __unicode__(self):
+ return self.name
+
+class CyclicTwo(models.Model):
+ name = models.CharField(max_length=25)
+ one = models.ForeignKey(CyclicOne)
+
+ def __unicode__(self):
+ return self.name
+
+class Topping(models.Model):
+ name = models.CharField(max_length=20)
+
+class Pizza(models.Model):
+ name = models.CharField(max_length=20)
+ toppings = models.ManyToManyField('Topping')
+
+class PizzaAdmin(admin.ModelAdmin):
+ readonly_fields = ('toppings',)
+
+class Album(models.Model):
+ owner = models.ForeignKey(User)
+ title = models.CharField(max_length=30)
+
+class AlbumAdmin(admin.ModelAdmin):
+ list_filter = ['title']
+
+admin.site.register(Article, ArticleAdmin)
+admin.site.register(CustomArticle, CustomArticleAdmin)
+admin.site.register(Section, save_as=True, inlines=[ArticleInline])
+admin.site.register(ModelWithStringPrimaryKey)
+admin.site.register(Color)
+admin.site.register(Thing, ThingAdmin)
+admin.site.register(Person, PersonAdmin)
+admin.site.register(Persona, PersonaAdmin)
+admin.site.register(Subscriber, SubscriberAdmin)
+admin.site.register(ExternalSubscriber, ExternalSubscriberAdmin)
+admin.site.register(OldSubscriber, OldSubscriberAdmin)
+admin.site.register(Podcast, PodcastAdmin)
+admin.site.register(Vodcast, VodcastAdmin)
+admin.site.register(Parent, ParentAdmin)
+admin.site.register(EmptyModel, EmptyModelAdmin)
+admin.site.register(Fabric, FabricAdmin)
+admin.site.register(Gallery, GalleryAdmin)
+admin.site.register(Picture, PictureAdmin)
+admin.site.register(Language, LanguageAdmin)
+admin.site.register(Recommendation, RecommendationAdmin)
+admin.site.register(Recommender)
+admin.site.register(Collector, CollectorAdmin)
+admin.site.register(Category, CategoryAdmin)
+admin.site.register(Post, PostAdmin)
+admin.site.register(Gadget, GadgetAdmin)
+admin.site.register(Villain)
+admin.site.register(SuperVillain)
+admin.site.register(Plot)
+admin.site.register(PlotDetails)
+admin.site.register(CyclicOne)
+admin.site.register(CyclicTwo)
+
+# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
+# That way we cover all four cases:
+# related ForeignKey object registered in admin
+# related ForeignKey object not registered in admin
+# related OneToOne object registered in admin
+# related OneToOne object not registered in admin
+# when deleting Book so as exercise all four troublesome (w.r.t escaping
+# and calling force_unicode to avoid problems on Python 2.3) paths through
+# contrib.admin.util's get_deleted_objects function.
+admin.site.register(Book, inlines=[ChapterInline])
+admin.site.register(Promo)
+admin.site.register(ChapterXtra1)
+admin.site.register(Pizza, PizzaAdmin)
+admin.site.register(Topping)
+admin.site.register(Album, AlbumAdmin)
diff --git a/parts/django/tests/regressiontests/admin_views/tests.py b/parts/django/tests/regressiontests/admin_views/tests.py
new file mode 100644
index 0000000..d3467dd
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/tests.py
@@ -0,0 +1,2287 @@
+# coding: utf-8
+
+import re
+import datetime
+
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation
+from django.core.files import temp as tempfile
+# Register auth models with the admin.
+from django.contrib.auth import REDIRECT_FIELD_NAME, admin
+from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.admin.models import LogEntry, DELETION
+from django.contrib.admin.sites import LOGIN_FORM_KEY
+from django.contrib.admin.util import quote
+from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
+from django.forms.util import ErrorList
+from django.test import TestCase
+from django.utils import formats
+from django.utils.cache import get_max_age
+from django.utils.encoding import iri_to_uri
+from django.utils.html import escape
+from django.utils.translation import activate, deactivate
+import django.template.context
+
+# local test models
+from models import Article, BarAccount, CustomArticle, EmptyModel, \
+ FooAccount, Gallery, ModelWithStringPrimaryKey, \
+ Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
+ Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
+ Category, Post, Plot, FunkyTag
+
+
+class AdminViewBasicTest(TestCase):
+ fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
+
+ # Store the bit of the URL where the admin is registered as a class
+ # variable. That way we can test a second AdminSite just by subclassing
+ # this test case and changing urlbit.
+ urlbit = 'admin'
+
+ def setUp(self):
+ self.old_language_code = settings.LANGUAGE_CODE
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ settings.LANGUAGE_CODE = self.old_language_code
+ self.client.logout()
+
+ def testTrailingSlashRequired(self):
+ """
+ If you leave off the trailing slash, app should redirect and add it.
+ """
+ request = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
+ self.assertRedirects(request,
+ '/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
+ )
+
+ def testBasicAddGet(self):
+ """
+ A smoke test to ensure GET on the add_view works.
+ """
+ response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
+ self.assertEqual(response.status_code, 200)
+
+ def testAddWithGETArgs(self):
+ response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ 'value="My Section"' in response.content,
+ "Couldn't find an input with the right value in the response."
+ )
+
+ def testBasicEditGet(self):
+ """
+ A smoke test to ensure GET on the change_view works.
+ """
+ response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
+ self.assertEqual(response.status_code, 200)
+
+ def testBasicEditGetStringPK(self):
+ """
+ A smoke test to ensure GET on the change_view works (returns an HTTP
+ 404 error, see #11191) when passing a string as the PK argument for a
+ model with an integer PK field.
+ """
+ response = self.client.get('/test_admin/%s/admin_views/section/abc/' % self.urlbit)
+ self.assertEqual(response.status_code, 404)
+
+ def testBasicAddPost(self):
+ """
+ A smoke test to ensure POST on add_view works.
+ """
+ post_data = {
+ "name": u"Another Section",
+ # inline data
+ "article_set-TOTAL_FORMS": u"3",
+ "article_set-INITIAL_FORMS": u"0",
+ "article_set-MAX_NUM_FORMS": u"0",
+ }
+ response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+ # Post data for edit inline
+ inline_post_data = {
+ "name": u"Test section",
+ # inline data
+ "article_set-TOTAL_FORMS": u"6",
+ "article_set-INITIAL_FORMS": u"3",
+ "article_set-MAX_NUM_FORMS": u"0",
+ "article_set-0-id": u"1",
+ # there is no title in database, give one here or formset will fail.
+ "article_set-0-title": u"Norske bostaver æøå skaper problemer",
+ "article_set-0-content": u"&lt;p&gt;Middle content&lt;/p&gt;",
+ "article_set-0-date_0": u"2008-03-18",
+ "article_set-0-date_1": u"11:54:58",
+ "article_set-0-section": u"1",
+ "article_set-1-id": u"2",
+ "article_set-1-title": u"Need a title.",
+ "article_set-1-content": u"&lt;p&gt;Oldest content&lt;/p&gt;",
+ "article_set-1-date_0": u"2000-03-18",
+ "article_set-1-date_1": u"11:54:58",
+ "article_set-2-id": u"3",
+ "article_set-2-title": u"Need a title.",
+ "article_set-2-content": u"&lt;p&gt;Newest content&lt;/p&gt;",
+ "article_set-2-date_0": u"2009-03-18",
+ "article_set-2-date_1": u"11:54:58",
+ "article_set-3-id": u"",
+ "article_set-3-title": u"",
+ "article_set-3-content": u"",
+ "article_set-3-date_0": u"",
+ "article_set-3-date_1": u"",
+ "article_set-4-id": u"",
+ "article_set-4-title": u"",
+ "article_set-4-content": u"",
+ "article_set-4-date_0": u"",
+ "article_set-4-date_1": u"",
+ "article_set-5-id": u"",
+ "article_set-5-title": u"",
+ "article_set-5-content": u"",
+ "article_set-5-date_0": u"",
+ "article_set-5-date_1": u"",
+ }
+
+ def testBasicEditPost(self):
+ """
+ A smoke test to ensure POST on edit_view works.
+ """
+ response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, self.inline_post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+ def testEditSaveAs(self):
+ """
+ Test "save as".
+ """
+ post_data = self.inline_post_data.copy()
+ post_data.update({
+ '_saveasnew': u'Save+as+new',
+ "article_set-1-section": u"1",
+ "article_set-2-section": u"1",
+ "article_set-3-section": u"1",
+ "article_set-4-section": u"1",
+ "article_set-5-section": u"1",
+ })
+ response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+ def testChangeListSortingCallable(self):
+ """
+ Ensure we can sort on a list_display field that is a callable
+ (column 2 is callable_year in ArticleAdmin)
+ """
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ response.content.index('Oldest content') < response.content.index('Middle content') and
+ response.content.index('Middle content') < response.content.index('Newest content'),
+ "Results of sorting on callable are out of order."
+ )
+
+ def testChangeListSortingModel(self):
+ """
+ Ensure we can sort on a list_display field that is a Model method
+ (colunn 3 is 'model_year' in ArticleAdmin)
+ """
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ response.content.index('Newest content') < response.content.index('Middle content') and
+ response.content.index('Middle content') < response.content.index('Oldest content'),
+ "Results of sorting on Model method are out of order."
+ )
+
+ def testChangeListSortingModelAdmin(self):
+ """
+ Ensure we can sort on a list_display field that is a ModelAdmin method
+ (colunn 4 is 'modeladmin_year' in ArticleAdmin)
+ """
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ response.content.index('Oldest content') < response.content.index('Middle content') and
+ response.content.index('Middle content') < response.content.index('Newest content'),
+ "Results of sorting on ModelAdmin method are out of order."
+ )
+
+ def testLimitedFilter(self):
+ """Ensure admin changelist filters do not contain objects excluded via limit_choices_to."""
+ response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ '<div id="changelist-filter">' in response.content,
+ "Expected filter not found in changelist view."
+ )
+ self.assertFalse(
+ '<a href="?color__id__exact=3">Blue</a>' in response.content,
+ "Changelist filter not correctly limited by limit_choices_to."
+ )
+
+ def testIncorrectLookupParameters(self):
+ """Ensure incorrect lookup parameters are handled gracefully."""
+ response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
+ self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
+ response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
+ self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
+
+ def testIsNullLookups(self):
+ """Ensure is_null is handled correctly."""
+ Article.objects.create(title="I Could Go Anywhere", content="Versatile", date=datetime.datetime.now())
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
+ self.assertTrue('4 articles' in response.content, '"4 articles" missing from response')
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'false'})
+ self.assertTrue('3 articles' in response.content, '"3 articles" missing from response')
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'true'})
+ self.assertTrue('1 article' in response.content, '"1 article" missing from response')
+
+ def testLogoutAndPasswordChangeURLs(self):
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
+ self.assertFalse('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
+ self.assertFalse('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
+
+ def testNamedGroupFieldChoicesChangeList(self):
+ """
+ Ensures the admin changelist shows correct values in the relevant column
+ for rows corresponding to instances of a model in which a named group
+ has been used in the choices option of a field.
+ """
+ response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ '<a href="1/">Horizontal</a>' in response.content and
+ '<a href="2/">Vertical</a>' in response.content,
+ "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
+ )
+
+ def testNamedGroupFieldChoicesFilter(self):
+ """
+ Ensures the filter UI shows correctly when at least one named group has
+ been used in the choices option of a model field.
+ """
+ response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ '<div id="changelist-filter">' in response.content,
+ "Expected filter not found in changelist view."
+ )
+ self.assertTrue(
+ '<a href="?surface__exact=x">Horizontal</a>' in response.content and
+ '<a href="?surface__exact=y">Vertical</a>' in response.content,
+ "Changelist filter isn't showing options contained inside a model field 'choices' option named group."
+ )
+
+ def testChangeListNullBooleanDisplay(self):
+ Post.objects.create(public=None)
+ # This hard-codes the URl because it'll fail if it runs
+ # against the 'admin2' custom admin (which doesn't have the
+ # Post model).
+ response = self.client.get("/test_admin/admin/admin_views/post/")
+ self.assertTrue('icon-unknown.gif' in response.content)
+
+ def testI18NLanguageNonEnglishDefault(self):
+ """
+ Check if the Javascript i18n view returns an empty language catalog
+ if the default language is non-English but the selected language
+ is English. See #13388 and #3594 for more details.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ activate('en-us')
+ response = self.client.get('/test_admin/admin/jsi18n/')
+ self.assertNotContains(response, 'Choisir une heure')
+ deactivate()
+
+ def testI18NLanguageNonEnglishFallback(self):
+ """
+ Makes sure that the fallback language is still working properly
+ in cases where the selected language cannot be found.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ activate('none')
+ response = self.client.get('/test_admin/admin/jsi18n/')
+ self.assertContains(response, 'Choisir une heure')
+ deactivate()
+
+ def test_disallowed_filtering(self):
+ self.assertRaises(SuspiciousOperation,
+ self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
+ )
+
+class SaveAsTests(TestCase):
+ fixtures = ['admin-views-users.xml','admin-views-person.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_save_as_duplication(self):
+ """Ensure save as actually creates a new person"""
+ post_data = {'_saveasnew':'', 'name':'John M', 'gender':1}
+ response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
+ self.assertEqual(len(Person.objects.filter(name='John M')), 1)
+ self.assertEqual(len(Person.objects.filter(id=1)), 1)
+
+ def test_save_as_display(self):
+ """
+ Ensure that 'save as' is displayed when activated and after submitting
+ invalid data aside save_as_new will not show us a form to overwrite the
+ initial model.
+ """
+ response = self.client.get('/test_admin/admin/admin_views/person/1/')
+ self.assert_(response.context['save_as'])
+ post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
+ response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
+ self.assertEqual(response.context['form_url'], '../add/')
+
+class CustomModelAdminTest(AdminViewBasicTest):
+ urlbit = "admin2"
+
+ def testCustomAdminSiteLoginTemplate(self):
+ self.client.logout()
+ request = self.client.get('/test_admin/admin2/')
+ self.assertTemplateUsed(request, 'custom_admin/login.html')
+ self.assert_('Hello from a custom login template' in request.content)
+
+ def testCustomAdminSiteLogoutTemplate(self):
+ request = self.client.get('/test_admin/admin2/logout/')
+ self.assertTemplateUsed(request, 'custom_admin/logout.html')
+ self.assert_('Hello from a custom logout template' in request.content)
+
+ def testCustomAdminSiteIndexViewAndTemplate(self):
+ request = self.client.get('/test_admin/admin2/')
+ self.assertTemplateUsed(request, 'custom_admin/index.html')
+ self.assert_('Hello from a custom index template *bar*' in request.content)
+
+ def testCustomAdminSitePasswordChangeTemplate(self):
+ request = self.client.get('/test_admin/admin2/password_change/')
+ self.assertTemplateUsed(request, 'custom_admin/password_change_form.html')
+ self.assert_('Hello from a custom password change form template' in request.content)
+
+ def testCustomAdminSitePasswordChangeDoneTemplate(self):
+ request = self.client.get('/test_admin/admin2/password_change/done/')
+ self.assertTemplateUsed(request, 'custom_admin/password_change_done.html')
+ self.assert_('Hello from a custom password change done template' in request.content)
+
+ def testCustomAdminSiteView(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
+ self.assert_(response.content == "Django is a magical pony!", response.content)
+
+def get_perm(Model, perm):
+ """Return the permission object, for the Model"""
+ ct = ContentType.objects.get_for_model(Model)
+ return Permission.objects.get(content_type=ct, codename=perm)
+
+class AdminViewPermissionsTest(TestCase):
+ """Tests for Admin Views Permissions."""
+
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ """Test setup."""
+ # Setup permissions, for our users who can add, change, and delete.
+ # We can't put this into the fixture, because the content type id
+ # and the permission id could be different on each run of the test.
+
+ opts = Article._meta
+
+ # User who can add Articles
+ add_user = User.objects.get(username='adduser')
+ add_user.user_permissions.add(get_perm(Article,
+ opts.get_add_permission()))
+
+ # User who can change Articles
+ change_user = User.objects.get(username='changeuser')
+ change_user.user_permissions.add(get_perm(Article,
+ opts.get_change_permission()))
+
+ # User who can delete Articles
+ delete_user = User.objects.get(username='deleteuser')
+ delete_user.user_permissions.add(get_perm(Article,
+ opts.get_delete_permission()))
+
+ delete_user.user_permissions.add(get_perm(Section,
+ Section._meta.get_delete_permission()))
+
+ # login POST dicts
+ self.super_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'super',
+ 'password': 'secret'}
+ self.super_email_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'super@example.com',
+ 'password': 'secret'}
+ self.super_email_bad_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'super@example.com',
+ 'password': 'notsecret'}
+ self.adduser_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'adduser',
+ 'password': 'secret'}
+ self.changeuser_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'changeuser',
+ 'password': 'secret'}
+ self.deleteuser_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'deleteuser',
+ 'password': 'secret'}
+ self.joepublic_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'joepublic',
+ 'password': 'secret'}
+ self.no_username_login = {
+ LOGIN_FORM_KEY: 1,
+ 'password': 'secret'}
+
+ def testLogin(self):
+ """
+ Make sure only staff members can log in.
+
+ Successful posts to the login page will redirect to the orignal url.
+ Unsuccessfull attempts will continue to render the login page with
+ a 200 status code.
+ """
+ # Super User
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.super_login)
+ self.assertRedirects(login, '/test_admin/admin/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Test if user enters e-mail address
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.super_email_login)
+ self.assertContains(login, "Your e-mail address is not your username")
+ # only correct passwords get a username hint
+ login = self.client.post('/test_admin/admin/', self.super_email_bad_login)
+ self.assertContains(login, "Please enter a correct username and password")
+ new_user = User(username='jondoe', password='secret', email='super@example.com')
+ new_user.save()
+ # check to ensure if there are multiple e-mail addresses a user doesn't get a 500
+ login = self.client.post('/test_admin/admin/', self.super_email_login)
+ self.assertContains(login, "Please enter a correct username and password")
+
+ # Add User
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.adduser_login)
+ self.assertRedirects(login, '/test_admin/admin/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Change User
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.changeuser_login)
+ self.assertRedirects(login, '/test_admin/admin/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Delete User
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.deleteuser_login)
+ self.assertRedirects(login, '/test_admin/admin/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Regular User should not be able to login.
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.joepublic_login)
+ self.assertEqual(login.status_code, 200)
+ self.assertContains(login, "Please enter a correct username and password.")
+
+ # Requests without username should not return 500 errors.
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/', self.no_username_login)
+ self.assertEqual(login.status_code, 200)
+ form = login.context[0].get('form')
+ self.assert_(login.context[0].get('error_message'))
+
+ def testLoginSuccessfullyRedirectsToOriginalUrl(self):
+ request = self.client.get('/test_admin/admin/')
+ self.assertEqual(request.status_code, 200)
+ query_string = 'the-answer=42'
+ redirect_url = '/test_admin/admin/?%s' % query_string
+ new_next = {REDIRECT_FIELD_NAME: redirect_url}
+ login = self.client.post('/test_admin/admin/', dict(self.super_login, **new_next), QUERY_STRING=query_string)
+ self.assertRedirects(login, redirect_url)
+
+ def testAddView(self):
+ """Test add view restricts access and actually adds items."""
+
+ add_dict = {'title' : 'Døm ikke',
+ 'content': '<p>great article</p>',
+ 'date_0': '2008-03-18', 'date_1': '10:54:39',
+ 'section': 1}
+
+ # Change User should not have access to add articles
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.changeuser_login)
+ # make sure the view removes test cookie
+ self.assertEqual(self.client.session.test_cookie_worked(), False)
+ request = self.client.get('/test_admin/admin/admin_views/article/add/')
+ self.assertEqual(request.status_code, 403)
+ # Try POST just to make sure
+ post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
+ self.assertEqual(post.status_code, 403)
+ self.assertEqual(Article.objects.all().count(), 3)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Add user may login and POST to add view, then redirect to admin root
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.adduser_login)
+ addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
+ self.assertEqual(addpage.status_code, 200)
+ change_list_link = '<a href="../">Articles</a> &rsaquo;'
+ self.assertFalse(change_list_link in addpage.content,
+ 'User restricted to add permission is given link to change list view in breadcrumbs.')
+ post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
+ self.assertRedirects(post, '/test_admin/admin/')
+ self.assertEqual(Article.objects.all().count(), 4)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Super can add too, but is redirected to the change list view
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.super_login)
+ addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
+ self.assertEqual(addpage.status_code, 200)
+ self.assertFalse(change_list_link not in addpage.content,
+ 'Unrestricted user is not given link to change list view in breadcrumbs.')
+ post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
+ self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
+ self.assertEqual(Article.objects.all().count(), 5)
+ self.client.get('/test_admin/admin/logout/')
+
+ # 8509 - if a normal user is already logged in, it is possible
+ # to change user into the superuser without error
+ login = self.client.login(username='joepublic', password='secret')
+ # Check and make sure that if user expires, data still persists
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.super_login)
+ # make sure the view removes test cookie
+ self.assertEqual(self.client.session.test_cookie_worked(), False)
+
+ def testChangeView(self):
+ """Change view should restrict access and allow users to edit items."""
+
+ change_dict = {'title' : 'Ikke fordømt',
+ 'content': '<p>edited article</p>',
+ 'date_0': '2008-03-18', 'date_1': '10:54:39',
+ 'section': 1}
+
+ # add user shoud not be able to view the list of article or change any of them
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.adduser_login)
+ request = self.client.get('/test_admin/admin/admin_views/article/')
+ self.assertEqual(request.status_code, 403)
+ request = self.client.get('/test_admin/admin/admin_views/article/1/')
+ self.assertEqual(request.status_code, 403)
+ post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
+ self.assertEqual(post.status_code, 403)
+ self.client.get('/test_admin/admin/logout/')
+
+ # change user can view all items and edit them
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.changeuser_login)
+ request = self.client.get('/test_admin/admin/admin_views/article/')
+ self.assertEqual(request.status_code, 200)
+ request = self.client.get('/test_admin/admin/admin_views/article/1/')
+ self.assertEqual(request.status_code, 200)
+ post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
+ self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
+ self.assertEqual(Article.objects.get(pk=1).content, '<p>edited article</p>')
+
+ # one error in form should produce singular error message, multiple errors plural
+ change_dict['title'] = ''
+ post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
+ self.assertEqual(request.status_code, 200)
+ self.assertTrue('Please correct the error below.' in post.content,
+ 'Singular error message not found in response to post with one error.')
+ change_dict['content'] = ''
+ post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
+ self.assertEqual(request.status_code, 200)
+ self.assertTrue('Please correct the errors below.' in post.content,
+ 'Plural error message not found in response to post with multiple errors.')
+ self.client.get('/test_admin/admin/logout/')
+
+ def testCustomModelAdminTemplates(self):
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.super_login)
+
+ # Test custom change list template with custom extra context
+ request = self.client.get('/test_admin/admin/admin_views/customarticle/')
+ self.assertEqual(request.status_code, 200)
+ self.assert_("var hello = 'Hello!';" in request.content)
+ self.assertTemplateUsed(request, 'custom_admin/change_list.html')
+
+ # Test custom add form template
+ request = self.client.get('/test_admin/admin/admin_views/customarticle/add/')
+ self.assertTemplateUsed(request, 'custom_admin/add_form.html')
+
+ # Add an article so we can test delete, change, and history views
+ post = self.client.post('/test_admin/admin/admin_views/customarticle/add/', {
+ 'content': '<p>great article</p>',
+ 'date_0': '2008-03-18',
+ 'date_1': '10:54:39'
+ })
+ self.assertRedirects(post, '/test_admin/admin/admin_views/customarticle/')
+ self.assertEqual(CustomArticle.objects.all().count(), 1)
+
+ # Test custom delete, change, and object history templates
+ # Test custom change form template
+ request = self.client.get('/test_admin/admin/admin_views/customarticle/1/')
+ self.assertTemplateUsed(request, 'custom_admin/change_form.html')
+ request = self.client.get('/test_admin/admin/admin_views/customarticle/1/delete/')
+ self.assertTemplateUsed(request, 'custom_admin/delete_confirmation.html')
+ request = self.client.post('/test_admin/admin/admin_views/customarticle/', data={
+ 'index': 0,
+ 'action': ['delete_selected'],
+ '_selected_action': ['1'],
+ })
+ self.assertTemplateUsed(request, 'custom_admin/delete_selected_confirmation.html')
+ request = self.client.get('/test_admin/admin/admin_views/customarticle/1/history/')
+ self.assertTemplateUsed(request, 'custom_admin/object_history.html')
+
+ self.client.get('/test_admin/admin/logout/')
+
+ def testDeleteView(self):
+ """Delete view should restrict access and actually delete items."""
+
+ delete_dict = {'post': 'yes'}
+
+ # add user shoud not be able to delete articles
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.adduser_login)
+ request = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
+ self.assertEqual(request.status_code, 403)
+ post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
+ self.assertEqual(post.status_code, 403)
+ self.assertEqual(Article.objects.all().count(), 3)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Delete user can delete
+ self.client.get('/test_admin/admin/')
+ self.client.post('/test_admin/admin/', self.deleteuser_login)
+ response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
+ # test response contains link to related Article
+ self.assertContains(response, "admin_views/article/1/")
+
+ response = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
+ self.assertEqual(response.status_code, 200)
+ post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
+ self.assertRedirects(post, '/test_admin/admin/')
+ self.assertEqual(Article.objects.all().count(), 2)
+ article_ct = ContentType.objects.get_for_model(Article)
+ logged = LogEntry.objects.get(content_type=article_ct, action_flag=DELETION)
+ self.assertEqual(logged.object_id, u'1')
+ self.client.get('/test_admin/admin/logout/')
+
+ def testDisabledPermissionsWhenLoggedIn(self):
+ self.client.login(username='super', password='secret')
+ superuser = User.objects.get(username='super')
+ superuser.is_active = False
+ superuser.save()
+
+ response = self.client.get('/test_admin/admin/')
+ self.assertContains(response, 'id="login-form"')
+ self.assertNotContains(response, 'Log out')
+
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertContains(response, 'id="login-form"')
+
+
+class AdminViewDeletedObjectsTest(TestCase):
+ fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_nesting(self):
+ """
+ Objects should be nested to display the relationships that
+ cause them to be scheduled for deletion.
+ """
+ pattern = re.compile(r"""<li>Plot: <a href=".+/admin_views/plot/1/">World Domination</a>\s*<ul>\s*<li>Plot details: <a href=".+/admin_views/plotdetails/1/">almost finished</a>""")
+ response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
+ self.assertTrue(pattern.search(response.content))
+
+ def test_cyclic(self):
+ """
+ Cyclic relationships should still cause each object to only be
+ listed once.
+
+ """
+ one = """<li>Cyclic one: <a href="/test_admin/admin/admin_views/cyclicone/1/">I am recursive</a>"""
+ two = """<li>Cyclic two: <a href="/test_admin/admin/admin_views/cyclictwo/1/">I am recursive too</a>"""
+ response = self.client.get('/test_admin/admin/admin_views/cyclicone/%s/delete/' % quote(1))
+
+ self.assertContains(response, one, 1)
+ self.assertContains(response, two, 1)
+
+ def test_perms_needed(self):
+ self.client.logout()
+ delete_user = User.objects.get(username='deleteuser')
+ delete_user.user_permissions.add(get_perm(Plot,
+ Plot._meta.get_delete_permission()))
+
+ self.assertTrue(self.client.login(username='deleteuser',
+ password='secret'))
+
+ response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(1))
+ self.assertContains(response, "your account doesn't have permission to delete the following types of objects")
+ self.assertContains(response, "<li>plot details</li>")
+
+
+ def test_not_registered(self):
+ should_contain = """<li>Secret hideout: underground bunker"""
+ response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
+ self.assertContains(response, should_contain, 1)
+
+ def test_multiple_fkeys_to_same_model(self):
+ """
+ If a deleted object has two relationships from another model,
+ both of those should be followed in looking for related
+ objects to delete.
+
+ """
+ should_contain = """<li>Plot: <a href="/test_admin/admin/admin_views/plot/1/">World Domination</a>"""
+ response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
+ self.assertContains(response, should_contain)
+ response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
+ self.assertContains(response, should_contain)
+
+ def test_multiple_fkeys_to_same_instance(self):
+ """
+ If a deleted object has two relationships pointing to it from
+ another object, the other object should still only be listed
+ once.
+
+ """
+ should_contain = """<li>Plot: <a href="/test_admin/admin/admin_views/plot/2/">World Peace</a></li>"""
+ response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
+ self.assertContains(response, should_contain, 1)
+
+ def test_inheritance(self):
+ """
+ In the case of an inherited model, if either the child or
+ parent-model instance is deleted, both instances are listed
+ for deletion, as well as any relationships they have.
+
+ """
+ should_contain = [
+ """<li>Villain: <a href="/test_admin/admin/admin_views/villain/3/">Bob</a>""",
+ """<li>Super villain: <a href="/test_admin/admin/admin_views/supervillain/3/">Bob</a>""",
+ """<li>Secret hideout: floating castle""",
+ """<li>Super secret hideout: super floating castle!"""
+ ]
+ response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(3))
+ for should in should_contain:
+ self.assertContains(response, should, 1)
+ response = self.client.get('/test_admin/admin/admin_views/supervillain/%s/delete/' % quote(3))
+ for should in should_contain:
+ self.assertContains(response, should, 1)
+
+ def test_generic_relations(self):
+ """
+ If a deleted object has GenericForeignKeys pointing to it,
+ those objects should be listed for deletion.
+
+ """
+ plot = Plot.objects.get(pk=3)
+ tag = FunkyTag.objects.create(content_object=plot, name='hott')
+ should_contain = """<li>Funky tag: hott"""
+ response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(3))
+ self.assertContains(response, should_contain)
+
+class AdminViewStringPrimaryKeyTest(TestCase):
+ fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
+
+ def __init__(self, *args):
+ super(AdminViewStringPrimaryKeyTest, self).__init__(*args)
+ self.pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`"""
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+ content_type_pk = ContentType.objects.get_for_model(ModelWithStringPrimaryKey).pk
+ LogEntry.objects.log_action(100, content_type_pk, self.pk, self.pk, 2, change_message='')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_get_history_view(self):
+ "Retrieving the history for the object using urlencoded form of primary key should work"
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk))
+ self.assertContains(response, escape(self.pk))
+ self.assertEqual(response.status_code, 200)
+
+ def test_get_change_view(self):
+ "Retrieving the object using urlencoded form of primary key should work"
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk))
+ self.assertContains(response, escape(self.pk))
+ self.assertEqual(response.status_code, 200)
+
+ def test_changelist_to_changeform_link(self):
+ "The link from the changelist referring to the changeform of the object should be quoted"
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
+ should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))
+ self.assertContains(response, should_contain)
+
+ def test_recentactions_link(self):
+ "The link from the recent actions list referring to the changeform of the object should be quoted"
+ response = self.client.get('/test_admin/admin/')
+ should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
+ self.assertContains(response, should_contain)
+
+ def test_recentactions_without_content_type(self):
+ "If a LogEntry is missing content_type it will not display it in span tag under the hyperlink."
+ response = self.client.get('/test_admin/admin/')
+ should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
+ self.assertContains(response, should_contain)
+ should_contain = "Model with string primary key" # capitalized in Recent Actions
+ self.assertContains(response, should_contain)
+ logentry = LogEntry.objects.get(content_type__name__iexact=should_contain)
+ # http://code.djangoproject.com/ticket/10275
+ # if the log entry doesn't have a content type it should still be
+ # possible to view the Recent Actions part
+ logentry.content_type = None
+ logentry.save()
+
+ counted_presence_before = response.content.count(should_contain)
+ response = self.client.get('/test_admin/admin/')
+ counted_presence_after = response.content.count(should_contain)
+ self.assertEquals(counted_presence_before - 1,
+ counted_presence_after)
+
+ def test_deleteconfirmation_link(self):
+ "The link from the delete confirmation page referring back to the changeform of the object should be quoted"
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk))
+ # this URL now comes through reverse(), thus iri_to_uri encoding
+ should_contain = """/%s/">%s</a>""" % (iri_to_uri(quote(self.pk)), escape(self.pk))
+ self.assertContains(response, should_contain)
+
+ def test_url_conflicts_with_add(self):
+ "A model with a primary key that ends with add should be visible"
+ add_model = ModelWithStringPrimaryKey(id="i have something to add")
+ add_model.save()
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(add_model.pk))
+ should_contain = """<h1>Change model with string primary key</h1>"""
+ self.assertContains(response, should_contain)
+
+ def test_url_conflicts_with_delete(self):
+ "A model with a primary key that ends with delete should be visible"
+ delete_model = ModelWithStringPrimaryKey(id="delete")
+ delete_model.save()
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(delete_model.pk))
+ should_contain = """<h1>Change model with string primary key</h1>"""
+ self.assertContains(response, should_contain)
+
+ def test_url_conflicts_with_history(self):
+ "A model with a primary key that ends with history should be visible"
+ history_model = ModelWithStringPrimaryKey(id="history")
+ history_model.save()
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(history_model.pk))
+ should_contain = """<h1>Change model with string primary key</h1>"""
+ self.assertContains(response, should_contain)
+
+
+class SecureViewTest(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ # login POST dicts
+ self.super_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'super',
+ 'password': 'secret'}
+ self.super_email_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'super@example.com',
+ 'password': 'secret'}
+ self.super_email_bad_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'super@example.com',
+ 'password': 'notsecret'}
+ self.adduser_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'adduser',
+ 'password': 'secret'}
+ self.changeuser_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'changeuser',
+ 'password': 'secret'}
+ self.deleteuser_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'deleteuser',
+ 'password': 'secret'}
+ self.joepublic_login = {
+ LOGIN_FORM_KEY: 1,
+ 'username': 'joepublic',
+ 'password': 'secret'}
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_secure_view_shows_login_if_not_logged_in(self):
+ "Ensure that we see the login form"
+ response = self.client.get('/test_admin/admin/secure-view/' )
+ self.assertTemplateUsed(response, 'admin/login.html')
+
+ def test_secure_view_login_successfully_redirects_to_original_url(self):
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ query_string = 'the-answer=42'
+ redirect_url = '/test_admin/admin/secure-view/?%s' % query_string
+ new_next = {REDIRECT_FIELD_NAME: redirect_url}
+ login = self.client.post('/test_admin/admin/secure-view/', dict(self.super_login, **new_next), QUERY_STRING=query_string)
+ self.assertRedirects(login, redirect_url)
+
+ def test_staff_member_required_decorator_works_as_per_admin_login(self):
+ """
+ Make sure only staff members can log in.
+
+ Successful posts to the login page will redirect to the orignal url.
+ Unsuccessfull attempts will continue to render the login page with
+ a 200 status code.
+ """
+ # Super User
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/secure-view/', self.super_login)
+ self.assertRedirects(login, '/test_admin/admin/secure-view/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+ # make sure the view removes test cookie
+ self.assertEqual(self.client.session.test_cookie_worked(), False)
+
+ # Test if user enters e-mail address
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
+ self.assertContains(login, "Your e-mail address is not your username")
+ # only correct passwords get a username hint
+ login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login)
+ self.assertContains(login, "Please enter a correct username and password")
+ new_user = User(username='jondoe', password='secret', email='super@example.com')
+ new_user.save()
+ # check to ensure if there are multiple e-mail addresses a user doesn't get a 500
+ login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
+ self.assertContains(login, "Please enter a correct username and password")
+
+ # Add User
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/secure-view/', self.adduser_login)
+ self.assertRedirects(login, '/test_admin/admin/secure-view/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Change User
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/secure-view/', self.changeuser_login)
+ self.assertRedirects(login, '/test_admin/admin/secure-view/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Delete User
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/secure-view/', self.deleteuser_login)
+ self.assertRedirects(login, '/test_admin/admin/secure-view/')
+ self.assertFalse(login.context)
+ self.client.get('/test_admin/admin/logout/')
+
+ # Regular User should not be able to login.
+ request = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(request.status_code, 200)
+ login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login)
+ self.assertEqual(login.status_code, 200)
+ # Login.context is a list of context dicts we just need to check the first one.
+ self.assert_(login.context[0].get('error_message'))
+
+ # 8509 - if a normal user is already logged in, it is possible
+ # to change user into the superuser without error
+ login = self.client.login(username='joepublic', password='secret')
+ # Check and make sure that if user expires, data still persists
+ self.client.get('/test_admin/admin/secure-view/')
+ self.client.post('/test_admin/admin/secure-view/', self.super_login)
+ # make sure the view removes test cookie
+ self.assertEqual(self.client.session.test_cookie_worked(), False)
+
+class AdminViewUnicodeTest(TestCase):
+ fixtures = ['admin-views-unicode.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testUnicodeEdit(self):
+ """
+ A test to ensure that POST on edit_view handles non-ascii characters.
+ """
+ post_data = {
+ "name": u"Test lærdommer",
+ # inline data
+ "chapter_set-TOTAL_FORMS": u"6",
+ "chapter_set-INITIAL_FORMS": u"3",
+ "chapter_set-MAX_NUM_FORMS": u"0",
+ "chapter_set-0-id": u"1",
+ "chapter_set-0-title": u"Norske bostaver æøå skaper problemer",
+ "chapter_set-0-content": u"&lt;p&gt;Svært frustrerende med UnicodeDecodeError&lt;/p&gt;",
+ "chapter_set-1-id": u"2",
+ "chapter_set-1-title": u"Kjærlighet.",
+ "chapter_set-1-content": u"&lt;p&gt;La kjærligheten til de lidende seire.&lt;/p&gt;",
+ "chapter_set-2-id": u"3",
+ "chapter_set-2-title": u"Need a title.",
+ "chapter_set-2-content": u"&lt;p&gt;Newest content&lt;/p&gt;",
+ "chapter_set-3-id": u"",
+ "chapter_set-3-title": u"",
+ "chapter_set-3-content": u"",
+ "chapter_set-4-id": u"",
+ "chapter_set-4-title": u"",
+ "chapter_set-4-content": u"",
+ "chapter_set-5-id": u"",
+ "chapter_set-5-title": u"",
+ "chapter_set-5-content": u"",
+ }
+
+ response = self.client.post('/test_admin/admin/admin_views/book/1/', post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+ def testUnicodeDelete(self):
+ """
+ Ensure that the delete_view handles non-ascii characters
+ """
+ delete_dict = {'post': 'yes'}
+ response = self.client.get('/test_admin/admin/admin_views/book/1/delete/')
+ self.assertEqual(response.status_code, 200)
+ response = self.client.post('/test_admin/admin/admin_views/book/1/delete/', delete_dict)
+ self.assertRedirects(response, '/test_admin/admin/admin_views/book/')
+
+
+class AdminViewListEditable(TestCase):
+ fixtures = ['admin-views-users.xml', 'admin-views-person.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_inheritance(self):
+ Podcast.objects.create(name="This Week in Django",
+ release_date=datetime.date.today())
+ response = self.client.get('/test_admin/admin/admin_views/podcast/')
+ self.assertEqual(response.status_code, 200)
+
+ def test_inheritance_2(self):
+ Vodcast.objects.create(name="This Week in Django", released=True)
+ response = self.client.get('/test_admin/admin/admin_views/vodcast/')
+ self.assertEqual(response.status_code, 200)
+
+ def test_custom_pk(self):
+ Language.objects.create(iso='en', name='English', english_name='English')
+ response = self.client.get('/test_admin/admin/admin_views/language/')
+ self.assertEqual(response.status_code, 200)
+
+ def test_changelist_input_html(self):
+ response = self.client.get('/test_admin/admin/admin_views/person/')
+ # 2 inputs per object(the field and the hidden id field) = 6
+ # 3 management hidden fields = 3
+ # 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
+ # main form submit button = 1
+ # search field and search submit button = 2
+ # CSRF field = 1
+ # field to track 'select all' across paginated views = 1
+ # 6 + 3 + 4 + 1 + 2 + 1 + 1 = 18 inputs
+ self.assertEqual(response.content.count("<input"), 18)
+ # 1 select per object = 3 selects
+ self.assertEqual(response.content.count("<select"), 4)
+
+ def test_post_messages(self):
+ # Ticket 12707: Saving inline editable should not show admin
+ # action warnings
+ data = {
+ "form-TOTAL_FORMS": "3",
+ "form-INITIAL_FORMS": "3",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-gender": "1",
+ "form-0-id": "1",
+
+ "form-1-gender": "2",
+ "form-1-id": "2",
+
+ "form-2-alive": "checked",
+ "form-2-gender": "1",
+ "form-2-id": "3",
+
+ "_save": "Save",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/person/',
+ data, follow=True)
+ self.assertEqual(len(response.context['messages']), 1)
+
+ def test_post_submission(self):
+ data = {
+ "form-TOTAL_FORMS": "3",
+ "form-INITIAL_FORMS": "3",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-gender": "1",
+ "form-0-id": "1",
+
+ "form-1-gender": "2",
+ "form-1-id": "2",
+
+ "form-2-alive": "checked",
+ "form-2-gender": "1",
+ "form-2-id": "3",
+
+ "_save": "Save",
+ }
+ self.client.post('/test_admin/admin/admin_views/person/', data)
+
+ self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
+ self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 2)
+
+ # test a filtered page
+ data = {
+ "form-TOTAL_FORMS": "2",
+ "form-INITIAL_FORMS": "2",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-id": "1",
+ "form-0-gender": "1",
+ "form-0-alive": "checked",
+
+ "form-1-id": "3",
+ "form-1-gender": "1",
+ "form-1-alive": "checked",
+
+ "_save": "Save",
+ }
+ self.client.post('/test_admin/admin/admin_views/person/?gender__exact=1', data)
+
+ self.assertEqual(Person.objects.get(name="John Mauchly").alive, True)
+
+ # test a searched page
+ data = {
+ "form-TOTAL_FORMS": "1",
+ "form-INITIAL_FORMS": "1",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-id": "1",
+ "form-0-gender": "1",
+
+ "_save": "Save",
+ }
+ self.client.post('/test_admin/admin/admin_views/person/?q=mauchly', data)
+
+ self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
+
+ def test_non_form_errors(self):
+ # test if non-form errors are handled; ticket #12716
+ data = {
+ "form-TOTAL_FORMS": "1",
+ "form-INITIAL_FORMS": "1",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-id": "2",
+ "form-0-alive": "1",
+ "form-0-gender": "2",
+
+ # Ensure that the form processing understands this as a list_editable "Save"
+ # and not an action "Go".
+ "_save": "Save",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/person/', data)
+ self.assertContains(response, "Grace is not a Zombie")
+
+ def test_non_form_errors_is_errorlist(self):
+ # test if non-form errors are correctly handled; ticket #12878
+ data = {
+ "form-TOTAL_FORMS": "1",
+ "form-INITIAL_FORMS": "1",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-id": "2",
+ "form-0-alive": "1",
+ "form-0-gender": "2",
+
+ "_save": "Save",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/person/', data)
+ non_form_errors = response.context['cl'].formset.non_form_errors()
+ self.assert_(isinstance(non_form_errors, ErrorList))
+ self.assertEqual(str(non_form_errors), str(ErrorList(["Grace is not a Zombie"])))
+
+ def test_list_editable_ordering(self):
+ collector = Collector.objects.create(id=1, name="Frederick Clegg")
+
+ Category.objects.create(id=1, order=1, collector=collector)
+ Category.objects.create(id=2, order=2, collector=collector)
+ Category.objects.create(id=3, order=0, collector=collector)
+ Category.objects.create(id=4, order=0, collector=collector)
+
+ # NB: The order values must be changed so that the items are reordered.
+ data = {
+ "form-TOTAL_FORMS": "4",
+ "form-INITIAL_FORMS": "4",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-order": "14",
+ "form-0-id": "1",
+ "form-0-collector": "1",
+
+ "form-1-order": "13",
+ "form-1-id": "2",
+ "form-1-collector": "1",
+
+ "form-2-order": "1",
+ "form-2-id": "3",
+ "form-2-collector": "1",
+
+ "form-3-order": "0",
+ "form-3-id": "4",
+ "form-3-collector": "1",
+
+ # Ensure that the form processing understands this as a list_editable "Save"
+ # and not an action "Go".
+ "_save": "Save",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/category/', data)
+ # Successful post will redirect
+ self.assertEqual(response.status_code, 302)
+
+ # Check that the order values have been applied to the right objects
+ self.assertEqual(Category.objects.get(id=1).order, 14)
+ self.assertEqual(Category.objects.get(id=2).order, 13)
+ self.assertEqual(Category.objects.get(id=3).order, 1)
+ self.assertEqual(Category.objects.get(id=4).order, 0)
+
+ def test_list_editable_action_submit(self):
+ # List editable changes should not be executed if the action "Go" button is
+ # used to submit the form.
+ data = {
+ "form-TOTAL_FORMS": "3",
+ "form-INITIAL_FORMS": "3",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-gender": "1",
+ "form-0-id": "1",
+
+ "form-1-gender": "2",
+ "form-1-id": "2",
+
+ "form-2-alive": "checked",
+ "form-2-gender": "1",
+ "form-2-id": "3",
+
+ "index": "0",
+ "_selected_action": [u'3'],
+ "action": [u'', u'delete_selected'],
+ }
+ self.client.post('/test_admin/admin/admin_views/person/', data)
+
+ self.assertEqual(Person.objects.get(name="John Mauchly").alive, True)
+ self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 1)
+
+ def test_list_editable_action_choices(self):
+ # List editable changes should be executed if the "Save" button is
+ # used to submit the form - any action choices should be ignored.
+ data = {
+ "form-TOTAL_FORMS": "3",
+ "form-INITIAL_FORMS": "3",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-gender": "1",
+ "form-0-id": "1",
+
+ "form-1-gender": "2",
+ "form-1-id": "2",
+
+ "form-2-alive": "checked",
+ "form-2-gender": "1",
+ "form-2-id": "3",
+
+ "_save": "Save",
+ "_selected_action": [u'1'],
+ "action": [u'', u'delete_selected'],
+ }
+ self.client.post('/test_admin/admin/admin_views/person/', data)
+
+ self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
+ self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 2)
+
+
+
+
+class AdminSearchTest(TestCase):
+ fixtures = ['admin-views-users','multiple-child-classes']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_search_on_sibling_models(self):
+ "Check that a search that mentions sibling models"
+ response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
+ # confirm the search returned 1 object
+ self.assertContains(response, "\n1 recommendation\n")
+
+class AdminInheritedInlinesTest(TestCase):
+ fixtures = ['admin-views-users.xml',]
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testInline(self):
+ "Ensure that inline models which inherit from a common parent are correctly handled by admin."
+
+ foo_user = u"foo username"
+ bar_user = u"bar username"
+
+ name_re = re.compile('name="(.*?)"')
+
+ # test the add case
+ response = self.client.get('/test_admin/admin/admin_views/persona/add/')
+ names = name_re.findall(response.content)
+ # make sure we have no duplicate HTML names
+ self.assertEqual(len(names), len(set(names)))
+
+ # test the add case
+ post_data = {
+ "name": u"Test Name",
+ # inline data
+ "accounts-TOTAL_FORMS": u"1",
+ "accounts-INITIAL_FORMS": u"0",
+ "accounts-MAX_NUM_FORMS": u"0",
+ "accounts-0-username": foo_user,
+ "accounts-2-TOTAL_FORMS": u"1",
+ "accounts-2-INITIAL_FORMS": u"0",
+ "accounts-2-MAX_NUM_FORMS": u"0",
+ "accounts-2-0-username": bar_user,
+ }
+
+ response = self.client.post('/test_admin/admin/admin_views/persona/add/', post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+ self.assertEqual(Persona.objects.count(), 1)
+ self.assertEqual(FooAccount.objects.count(), 1)
+ self.assertEqual(BarAccount.objects.count(), 1)
+ self.assertEqual(FooAccount.objects.all()[0].username, foo_user)
+ self.assertEqual(BarAccount.objects.all()[0].username, bar_user)
+ self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
+
+ # test the edit case
+
+ response = self.client.get('/test_admin/admin/admin_views/persona/1/')
+ names = name_re.findall(response.content)
+ # make sure we have no duplicate HTML names
+ self.assertEqual(len(names), len(set(names)))
+
+ post_data = {
+ "name": u"Test Name",
+
+ "accounts-TOTAL_FORMS": "2",
+ "accounts-INITIAL_FORMS": u"1",
+ "accounts-MAX_NUM_FORMS": u"0",
+
+ "accounts-0-username": "%s-1" % foo_user,
+ "accounts-0-account_ptr": "1",
+ "accounts-0-persona": "1",
+
+ "accounts-2-TOTAL_FORMS": u"2",
+ "accounts-2-INITIAL_FORMS": u"1",
+ "accounts-2-MAX_NUM_FORMS": u"0",
+
+ "accounts-2-0-username": "%s-1" % bar_user,
+ "accounts-2-0-account_ptr": "2",
+ "accounts-2-0-persona": "1",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/persona/1/', post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Persona.objects.count(), 1)
+ self.assertEqual(FooAccount.objects.count(), 1)
+ self.assertEqual(BarAccount.objects.count(), 1)
+ self.assertEqual(FooAccount.objects.all()[0].username, "%s-1" % foo_user)
+ self.assertEqual(BarAccount.objects.all()[0].username, "%s-1" % bar_user)
+ self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
+
+from django.core import mail
+
+class AdminActionsTest(TestCase):
+ fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_model_admin_custom_action(self):
+ "Tests a custom action defined in a ModelAdmin method"
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action' : 'mail_admin',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
+ self.assertEquals(len(mail.outbox), 1)
+ self.assertEquals(mail.outbox[0].subject, 'Greetings from a ModelAdmin action')
+
+ def test_model_admin_default_delete_action(self):
+ "Tests the default delete action defined as a ModelAdmin method"
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1, 2],
+ 'action' : 'delete_selected',
+ 'index': 0,
+ }
+ delete_confirmation_data = {
+ ACTION_CHECKBOX_NAME: [1, 2],
+ 'action' : 'delete_selected',
+ 'post': 'yes',
+ }
+ confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
+ self.assertContains(confirmation, "Are you sure you want to delete the selected subscriber objects")
+ self.assertTrue(confirmation.content.count(ACTION_CHECKBOX_NAME) == 2)
+ response = self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data)
+ self.assertEqual(Subscriber.objects.count(), 0)
+
+ def test_custom_function_mail_action(self):
+ "Tests a custom action defined in a function"
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action' : 'external_mail',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+ self.assertEquals(len(mail.outbox), 1)
+ self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action')
+
+ def test_custom_function_action_with_redirect(self):
+ "Tests a custom action defined in a function"
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action' : 'redirect_to',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+ self.assertEqual(response.status_code, 302)
+
+ def test_default_redirect(self):
+ """
+ Test that actions which don't return an HttpResponse are redirected to
+ the same page, retaining the querystring (which may contain changelist
+ information).
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action' : 'external_mail',
+ 'index': 0,
+ }
+ url = '/test_admin/admin/admin_views/externalsubscriber/?ot=asc&o=1'
+ response = self.client.post(url, action_data)
+ self.assertRedirects(response, url)
+
+ def test_model_without_action(self):
+ "Tests a ModelAdmin without any action"
+ response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
+ self.assertEquals(response.context["action_form"], None)
+ self.assert_(
+ '<input type="checkbox" class="action-select"' not in response.content,
+ "Found an unexpected action toggle checkboxbox in response"
+ )
+ self.assert_('action-checkbox-column' not in response.content,
+ "Found unexpected action-checkbox-column class in response")
+
+ def test_model_without_action_still_has_jquery(self):
+ "Tests that a ModelAdmin without any actions still gets jQuery included in page"
+ response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
+ self.assertEquals(response.context["action_form"], None)
+ self.assert_('jquery.min.js' in response.content,
+ "jQuery missing from admin pages for model with no admin actions"
+ )
+
+ def test_action_column_class(self):
+ "Tests that the checkbox column class is present in the response"
+ response = self.client.get('/test_admin/admin/admin_views/subscriber/')
+ self.assertNotEqual(response.context["action_form"], None)
+ self.assert_('action-checkbox-column' in response.content,
+ "Expected an action-checkbox-column in response")
+
+ def test_multiple_actions_form(self):
+ """
+ Test that actions come from the form whose submit button was pressed (#10618).
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ # Two different actions selected on the two forms...
+ 'action': ['external_mail', 'delete_selected'],
+ # ...but we clicked "go" on the top form.
+ 'index': 0
+ }
+ response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+
+ # Send mail, don't delete.
+ self.assertEquals(len(mail.outbox), 1)
+ self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action')
+
+ def test_user_message_on_none_selected(self):
+ """
+ User should see a warning when 'Go' is pressed and no items are selected.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [],
+ 'action' : 'delete_selected',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
+ msg = """Items must be selected in order to perform actions on them. No items have been changed."""
+ self.assertContains(response, msg)
+ self.assertEqual(Subscriber.objects.count(), 2)
+
+ def test_user_message_on_no_action(self):
+ """
+ User should see a warning when 'Go' is pressed and no action is selected.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1, 2],
+ 'action' : '',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
+ msg = """No action selected."""
+ self.assertContains(response, msg)
+ self.assertEqual(Subscriber.objects.count(), 2)
+
+ def test_selection_counter(self):
+ """
+ Check if the selection counter is there.
+ """
+ response = self.client.get('/test_admin/admin/admin_views/subscriber/')
+ self.assertContains(response, '0 of 2 selected')
+
+
+class TestCustomChangeList(TestCase):
+ fixtures = ['admin-views-users.xml']
+ urlbit = 'admin'
+
+ def setUp(self):
+ result = self.client.login(username='super', password='secret')
+ self.assertEqual(result, True)
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_custom_changelist(self):
+ """
+ Validate that a custom ChangeList class can be used (#9749)
+ """
+ # Insert some data
+ post_data = {"name": u"First Gadget"}
+ response = self.client.post('/test_admin/%s/admin_views/gadget/add/' % self.urlbit, post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+ # Hit the page once to get messages out of the queue message list
+ response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
+ # Ensure that that data is still not visible on the page
+ response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, 'First Gadget')
+
+
+class TestInlineNotEditable(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ result = self.client.login(username='super', password='secret')
+ self.assertEqual(result, True)
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test(self):
+ """
+ InlineModelAdmin broken?
+ """
+ response = self.client.get('/test_admin/admin/admin_views/parent/add/')
+ self.assertEqual(response.status_code, 200)
+
+class AdminCustomQuerysetTest(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+ self.pks = [EmptyModel.objects.create().id for i in range(3)]
+
+ def test_changelist_view(self):
+ response = self.client.get('/test_admin/admin/admin_views/emptymodel/')
+ for i in self.pks:
+ if i > 1:
+ self.assertContains(response, 'Primary key = %s' % i)
+ else:
+ self.assertNotContains(response, 'Primary key = %s' % i)
+
+ def test_change_view(self):
+ for i in self.pks:
+ response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i)
+ if i > 1:
+ self.assertEqual(response.status_code, 200)
+ else:
+ self.assertEqual(response.status_code, 404)
+
+class AdminInlineFileUploadTest(TestCase):
+ fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
+ urlbit = 'admin'
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ # Set up test Picture and Gallery.
+ # These must be set up here instead of in fixtures in order to allow Picture
+ # to use a NamedTemporaryFile.
+ tdir = tempfile.gettempdir()
+ file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
+ file1.write('a' * (2 ** 21))
+ filename = file1.name
+ file1.close()
+ g = Gallery(name="Test Gallery")
+ g.save()
+ p = Picture(name="Test Picture", image=filename, gallery=g)
+ p.save()
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_inline_file_upload_edit_validation_error_post(self):
+ """
+ Test that inline file uploads correctly display prior data (#10002).
+ """
+ post_data = {
+ "name": u"Test Gallery",
+ "pictures-TOTAL_FORMS": u"2",
+ "pictures-INITIAL_FORMS": u"1",
+ "pictures-MAX_NUM_FORMS": u"0",
+ "pictures-0-id": u"1",
+ "pictures-0-gallery": u"1",
+ "pictures-0-name": "Test Picture",
+ "pictures-0-image": "",
+ "pictures-1-id": "",
+ "pictures-1-gallery": "1",
+ "pictures-1-name": "Test Picture 2",
+ "pictures-1-image": "",
+ }
+ response = self.client.post('/test_admin/%s/admin_views/gallery/1/' % self.urlbit, post_data)
+ self.assertTrue(response._container[0].find("Currently:") > -1)
+
+
+class AdminInlineTests(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.post_data = {
+ "name": u"Test Name",
+
+ "widget_set-TOTAL_FORMS": "3",
+ "widget_set-INITIAL_FORMS": u"0",
+ "widget_set-MAX_NUM_FORMS": u"0",
+ "widget_set-0-id": "",
+ "widget_set-0-owner": "1",
+ "widget_set-0-name": "",
+ "widget_set-1-id": "",
+ "widget_set-1-owner": "1",
+ "widget_set-1-name": "",
+ "widget_set-2-id": "",
+ "widget_set-2-owner": "1",
+ "widget_set-2-name": "",
+
+ "doohickey_set-TOTAL_FORMS": "3",
+ "doohickey_set-INITIAL_FORMS": u"0",
+ "doohickey_set-MAX_NUM_FORMS": u"0",
+ "doohickey_set-0-owner": "1",
+ "doohickey_set-0-code": "",
+ "doohickey_set-0-name": "",
+ "doohickey_set-1-owner": "1",
+ "doohickey_set-1-code": "",
+ "doohickey_set-1-name": "",
+ "doohickey_set-2-owner": "1",
+ "doohickey_set-2-code": "",
+ "doohickey_set-2-name": "",
+
+ "grommet_set-TOTAL_FORMS": "3",
+ "grommet_set-INITIAL_FORMS": u"0",
+ "grommet_set-MAX_NUM_FORMS": u"0",
+ "grommet_set-0-code": "",
+ "grommet_set-0-owner": "1",
+ "grommet_set-0-name": "",
+ "grommet_set-1-code": "",
+ "grommet_set-1-owner": "1",
+ "grommet_set-1-name": "",
+ "grommet_set-2-code": "",
+ "grommet_set-2-owner": "1",
+ "grommet_set-2-name": "",
+
+ "whatsit_set-TOTAL_FORMS": "3",
+ "whatsit_set-INITIAL_FORMS": u"0",
+ "whatsit_set-MAX_NUM_FORMS": u"0",
+ "whatsit_set-0-owner": "1",
+ "whatsit_set-0-index": "",
+ "whatsit_set-0-name": "",
+ "whatsit_set-1-owner": "1",
+ "whatsit_set-1-index": "",
+ "whatsit_set-1-name": "",
+ "whatsit_set-2-owner": "1",
+ "whatsit_set-2-index": "",
+ "whatsit_set-2-name": "",
+
+ "fancydoodad_set-TOTAL_FORMS": "3",
+ "fancydoodad_set-INITIAL_FORMS": u"0",
+ "fancydoodad_set-MAX_NUM_FORMS": u"0",
+ "fancydoodad_set-0-doodad_ptr": "",
+ "fancydoodad_set-0-owner": "1",
+ "fancydoodad_set-0-name": "",
+ "fancydoodad_set-0-expensive": "on",
+ "fancydoodad_set-1-doodad_ptr": "",
+ "fancydoodad_set-1-owner": "1",
+ "fancydoodad_set-1-name": "",
+ "fancydoodad_set-1-expensive": "on",
+ "fancydoodad_set-2-doodad_ptr": "",
+ "fancydoodad_set-2-owner": "1",
+ "fancydoodad_set-2-name": "",
+ "fancydoodad_set-2-expensive": "on",
+
+ "category_set-TOTAL_FORMS": "3",
+ "category_set-INITIAL_FORMS": "0",
+ "category_set-MAX_NUM_FORMS": "0",
+ "category_set-0-order": "",
+ "category_set-0-id": "",
+ "category_set-0-collector": "1",
+ "category_set-1-order": "",
+ "category_set-1-id": "",
+ "category_set-1-collector": "1",
+ "category_set-2-order": "",
+ "category_set-2-id": "",
+ "category_set-2-collector": "1",
+ }
+
+ result = self.client.login(username='super', password='secret')
+ self.assertEqual(result, True)
+ self.collector = Collector(pk=1,name='John Fowles')
+ self.collector.save()
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_simple_inline(self):
+ "A simple model can be saved as inlines"
+ # First add a new inline
+ self.post_data['widget_set-0-name'] = "Widget 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Widget.objects.count(), 1)
+ self.assertEqual(Widget.objects.all()[0].name, "Widget 1")
+
+ # Check that the PK link exists on the rendered form
+ response = self.client.get('/test_admin/admin/admin_views/collector/1/')
+ self.assertContains(response, 'name="widget_set-0-id"')
+
+ # Now resave that inline
+ self.post_data['widget_set-INITIAL_FORMS'] = "1"
+ self.post_data['widget_set-0-id'] = "1"
+ self.post_data['widget_set-0-name'] = "Widget 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Widget.objects.count(), 1)
+ self.assertEqual(Widget.objects.all()[0].name, "Widget 1")
+
+ # Now modify that inline
+ self.post_data['widget_set-INITIAL_FORMS'] = "1"
+ self.post_data['widget_set-0-id'] = "1"
+ self.post_data['widget_set-0-name'] = "Widget 1 Updated"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Widget.objects.count(), 1)
+ self.assertEqual(Widget.objects.all()[0].name, "Widget 1 Updated")
+
+ def test_explicit_autofield_inline(self):
+ "A model with an explicit autofield primary key can be saved as inlines. Regression for #8093"
+ # First add a new inline
+ self.post_data['grommet_set-0-name'] = "Grommet 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Grommet.objects.count(), 1)
+ self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1")
+
+ # Check that the PK link exists on the rendered form
+ response = self.client.get('/test_admin/admin/admin_views/collector/1/')
+ self.assertContains(response, 'name="grommet_set-0-code"')
+
+ # Now resave that inline
+ self.post_data['grommet_set-INITIAL_FORMS'] = "1"
+ self.post_data['grommet_set-0-code'] = "1"
+ self.post_data['grommet_set-0-name'] = "Grommet 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Grommet.objects.count(), 1)
+ self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1")
+
+ # Now modify that inline
+ self.post_data['grommet_set-INITIAL_FORMS'] = "1"
+ self.post_data['grommet_set-0-code'] = "1"
+ self.post_data['grommet_set-0-name'] = "Grommet 1 Updated"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Grommet.objects.count(), 1)
+ self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1 Updated")
+
+ def test_char_pk_inline(self):
+ "A model with a character PK can be saved as inlines. Regression for #10992"
+ # First add a new inline
+ self.post_data['doohickey_set-0-code'] = "DH1"
+ self.post_data['doohickey_set-0-name'] = "Doohickey 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(DooHickey.objects.count(), 1)
+ self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1")
+
+ # Check that the PK link exists on the rendered form
+ response = self.client.get('/test_admin/admin/admin_views/collector/1/')
+ self.assertContains(response, 'name="doohickey_set-0-code"')
+
+ # Now resave that inline
+ self.post_data['doohickey_set-INITIAL_FORMS'] = "1"
+ self.post_data['doohickey_set-0-code'] = "DH1"
+ self.post_data['doohickey_set-0-name'] = "Doohickey 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(DooHickey.objects.count(), 1)
+ self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1")
+
+ # Now modify that inline
+ self.post_data['doohickey_set-INITIAL_FORMS'] = "1"
+ self.post_data['doohickey_set-0-code'] = "DH1"
+ self.post_data['doohickey_set-0-name'] = "Doohickey 1 Updated"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(DooHickey.objects.count(), 1)
+ self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1 Updated")
+
+ def test_integer_pk_inline(self):
+ "A model with an integer PK can be saved as inlines. Regression for #10992"
+ # First add a new inline
+ self.post_data['whatsit_set-0-index'] = "42"
+ self.post_data['whatsit_set-0-name'] = "Whatsit 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Whatsit.objects.count(), 1)
+ self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1")
+
+ # Check that the PK link exists on the rendered form
+ response = self.client.get('/test_admin/admin/admin_views/collector/1/')
+ self.assertContains(response, 'name="whatsit_set-0-index"')
+
+ # Now resave that inline
+ self.post_data['whatsit_set-INITIAL_FORMS'] = "1"
+ self.post_data['whatsit_set-0-index'] = "42"
+ self.post_data['whatsit_set-0-name'] = "Whatsit 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Whatsit.objects.count(), 1)
+ self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1")
+
+ # Now modify that inline
+ self.post_data['whatsit_set-INITIAL_FORMS'] = "1"
+ self.post_data['whatsit_set-0-index'] = "42"
+ self.post_data['whatsit_set-0-name'] = "Whatsit 1 Updated"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Whatsit.objects.count(), 1)
+ self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1 Updated")
+
+ def test_inherited_inline(self):
+ "An inherited model can be saved as inlines. Regression for #11042"
+ # First add a new inline
+ self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(FancyDoodad.objects.count(), 1)
+ self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1")
+
+ # Check that the PK link exists on the rendered form
+ response = self.client.get('/test_admin/admin/admin_views/collector/1/')
+ self.assertContains(response, 'name="fancydoodad_set-0-doodad_ptr"')
+
+ # Now resave that inline
+ self.post_data['fancydoodad_set-INITIAL_FORMS'] = "1"
+ self.post_data['fancydoodad_set-0-doodad_ptr'] = "1"
+ self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(FancyDoodad.objects.count(), 1)
+ self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1")
+
+ # Now modify that inline
+ self.post_data['fancydoodad_set-INITIAL_FORMS'] = "1"
+ self.post_data['fancydoodad_set-0-doodad_ptr'] = "1"
+ self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1 Updated"
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(FancyDoodad.objects.count(), 1)
+ self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1 Updated")
+
+ def test_ordered_inline(self):
+ """Check that an inline with an editable ordering fields is
+ updated correctly. Regression for #10922"""
+ # Create some objects with an initial ordering
+ Category.objects.create(id=1, order=1, collector=self.collector)
+ Category.objects.create(id=2, order=2, collector=self.collector)
+ Category.objects.create(id=3, order=0, collector=self.collector)
+ Category.objects.create(id=4, order=0, collector=self.collector)
+
+ # NB: The order values must be changed so that the items are reordered.
+ self.post_data.update({
+ "name": "Frederick Clegg",
+
+ "category_set-TOTAL_FORMS": "7",
+ "category_set-INITIAL_FORMS": "4",
+ "category_set-MAX_NUM_FORMS": "0",
+
+ "category_set-0-order": "14",
+ "category_set-0-id": "1",
+ "category_set-0-collector": "1",
+
+ "category_set-1-order": "13",
+ "category_set-1-id": "2",
+ "category_set-1-collector": "1",
+
+ "category_set-2-order": "1",
+ "category_set-2-id": "3",
+ "category_set-2-collector": "1",
+
+ "category_set-3-order": "0",
+ "category_set-3-id": "4",
+ "category_set-3-collector": "1",
+
+ "category_set-4-order": "",
+ "category_set-4-id": "",
+ "category_set-4-collector": "1",
+
+ "category_set-5-order": "",
+ "category_set-5-id": "",
+ "category_set-5-collector": "1",
+
+ "category_set-6-order": "",
+ "category_set-6-id": "",
+ "category_set-6-collector": "1",
+ })
+ response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
+ # Successful post will redirect
+ self.assertEqual(response.status_code, 302)
+
+ # Check that the order values have been applied to the right objects
+ self.assertEqual(self.collector.category_set.count(), 4)
+ self.assertEqual(Category.objects.get(id=1).order, 14)
+ self.assertEqual(Category.objects.get(id=2).order, 13)
+ self.assertEqual(Category.objects.get(id=3).order, 1)
+ self.assertEqual(Category.objects.get(id=4).order, 0)
+
+
+class NeverCacheTests(TestCase):
+ fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testAdminIndex(self):
+ "Check the never-cache status of the main index"
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testAppIndex(self):
+ "Check the never-cache status of an application index"
+ response = self.client.get('/test_admin/admin/admin_views/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testModelIndex(self):
+ "Check the never-cache status of a model index"
+ response = self.client.get('/test_admin/admin/admin_views/fabric/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testModelAdd(self):
+ "Check the never-cache status of a model add page"
+ response = self.client.get('/test_admin/admin/admin_views/fabric/add/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testModelView(self):
+ "Check the never-cache status of a model edit page"
+ response = self.client.get('/test_admin/admin/admin_views/section/1/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testModelHistory(self):
+ "Check the never-cache status of a model history page"
+ response = self.client.get('/test_admin/admin/admin_views/section/1/history/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testModelDelete(self):
+ "Check the never-cache status of a model delete page"
+ response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testLogin(self):
+ "Check the never-cache status of login views"
+ self.client.logout()
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testLogout(self):
+ "Check the never-cache status of logout view"
+ response = self.client.get('/test_admin/admin/logout/')
+ self.assertEqual(get_max_age(response), 0)
+
+ def testPasswordChange(self):
+ "Check the never-cache status of the password change view"
+ self.client.logout()
+ response = self.client.get('/test_admin/password_change/')
+ self.assertEqual(get_max_age(response), None)
+
+ def testPasswordChangeDone(self):
+ "Check the never-cache status of the password change done view"
+ response = self.client.get('/test_admin/admin/password_change/done/')
+ self.assertEqual(get_max_age(response), None)
+
+ def testJsi18n(self):
+ "Check the never-cache status of the Javascript i18n view"
+ response = self.client.get('/test_admin/admin/jsi18n/')
+ self.assertEqual(get_max_age(response), None)
+
+
+class ReadonlyTest(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_readonly_get(self):
+ response = self.client.get('/test_admin/admin/admin_views/post/add/')
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, 'name="posted"')
+ # 3 fields + 2 submit buttons + 4 inline management form fields, + 2
+ # hidden fields for inlines + 1 field for the inline + 2 empty form
+ self.assertEqual(response.content.count("<input"), 14)
+ self.assertContains(response, formats.localize(datetime.date.today()))
+ self.assertContains(response,
+ "<label>Awesomeness level:</label>")
+ self.assertContains(response, "Very awesome.")
+ self.assertContains(response, "Unkown coolness.")
+ self.assertContains(response, "foo")
+ self.assertContains(response,
+ formats.localize(datetime.date.today() - datetime.timedelta(days=7))
+ )
+
+ self.assertContains(response, '<div class="form-row coolness">')
+ self.assertContains(response, '<div class="form-row awesomeness_level">')
+ self.assertContains(response, '<div class="form-row posted">')
+ self.assertContains(response, '<div class="form-row value">')
+ self.assertContains(response, '<div class="form-row ">')
+
+ p = Post.objects.create(title="I worked on readonly_fields", content="Its good stuff")
+ response = self.client.get('/test_admin/admin/admin_views/post/%d/' % p.pk)
+ self.assertContains(response, "%d amount of cool" % p.pk)
+
+ def test_readonly_post(self):
+ data = {
+ "title": "Django Got Readonly Fields",
+ "content": "This is an incredible development.",
+ "link_set-TOTAL_FORMS": "1",
+ "link_set-INITIAL_FORMS": "0",
+ "link_set-MAX_NUM_FORMS": "0",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Post.objects.count(), 1)
+ p = Post.objects.get()
+ self.assertEqual(p.posted, datetime.date.today())
+
+ data["posted"] = "10-8-1990" # some date that's not today
+ response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Post.objects.count(), 2)
+ p = Post.objects.order_by('-id')[0]
+ self.assertEqual(p.posted, datetime.date.today())
+
+ def test_readonly_manytomany(self):
+ "Regression test for #13004"
+ response = self.client.get('/test_admin/admin/admin_views/pizza/add/')
+ self.assertEqual(response.status_code, 200)
+
+class UserAdminTest(TestCase):
+ """
+ Tests user CRUD functionality.
+ """
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_save_button(self):
+ user_count = User.objects.count()
+ response = self.client.post('/test_admin/admin/auth/user/add/', {
+ 'username': 'newuser',
+ 'password1': 'newpassword',
+ 'password2': 'newpassword',
+ })
+ new_user = User.objects.order_by('-id')[0]
+ self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
+ self.assertEqual(User.objects.count(), user_count + 1)
+ self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
+
+ def test_save_continue_editing_button(self):
+ user_count = User.objects.count()
+ response = self.client.post('/test_admin/admin/auth/user/add/', {
+ 'username': 'newuser',
+ 'password1': 'newpassword',
+ 'password2': 'newpassword',
+ '_continue': '1',
+ })
+ new_user = User.objects.order_by('-id')[0]
+ self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
+ self.assertEqual(User.objects.count(), user_count + 1)
+ self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
+
+ def test_password_mismatch(self):
+ response = self.client.post('/test_admin/admin/auth/user/add/', {
+ 'username': 'newuser',
+ 'password1': 'newpassword',
+ 'password2': 'mismatch',
+ })
+ self.assertEqual(response.status_code, 200)
+ adminform = response.context['adminform']
+ self.assertTrue('password' not in adminform.form.errors)
+ self.assertEqual(adminform.form.errors['password2'],
+ [u"The two password fields didn't match."])
+
+ def test_user_fk_popup(self):
+ response = self.client.get('/test_admin/admin/admin_views/album/add/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, '/test_admin/admin/auth/user/add')
+ self.assertContains(response, 'class="add-another" id="add_id_owner" onclick="return showAddAnotherPopup(this);"')
+ response = self.client.get('/test_admin/admin/auth/user/add/?_popup=1')
+ self.assertNotContains(response, 'name="_continue"')
+ self.assertNotContains(response, 'name="_addanother"')
+
+ def test_save_add_another_button(self):
+ user_count = User.objects.count()
+ response = self.client.post('/test_admin/admin/auth/user/add/', {
+ 'username': 'newuser',
+ 'password1': 'newpassword',
+ 'password2': 'newpassword',
+ '_addanother': '1',
+ })
+ new_user = User.objects.order_by('-id')[0]
+ self.assertRedirects(response, '/test_admin/admin/auth/user/add/')
+ self.assertEqual(User.objects.count(), user_count + 1)
+ self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
+
+try:
+ # If docutils isn't installed, skip the AdminDocs tests.
+ import docutils
+
+ class AdminDocsTest(TestCase):
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_tags(self):
+ response = self.client.get('/test_admin/admin/doc/tags/')
+
+ # The builtin tag group exists
+ self.assertContains(response, "<h2>Built-in tags</h2>", count=2)
+
+ # A builtin tag exists in both the index and detail
+ self.assertContains(response, '<h3 id="built_in-autoescape">autoescape</h3>')
+ self.assertContains(response, '<li><a href="#built_in-autoescape">autoescape</a></li>')
+
+ # An app tag exists in both the index and detail
+ self.assertContains(response, '<h3 id="comments-get_comment_count">get_comment_count</h3>')
+ self.assertContains(response, '<li><a href="#comments-get_comment_count">get_comment_count</a></li>')
+
+ # The admin list tag group exists
+ self.assertContains(response, "<h2>admin_list</h2>", count=2)
+
+ # An admin list tag exists in both the index and detail
+ self.assertContains(response, '<h3 id="admin_list-admin_actions">admin_actions</h3>')
+ self.assertContains(response, '<li><a href="#admin_list-admin_actions">admin_actions</a></li>')
+
+ def test_filters(self):
+ response = self.client.get('/test_admin/admin/doc/filters/')
+
+ # The builtin filter group exists
+ self.assertContains(response, "<h2>Built-in filters</h2>", count=2)
+
+ # A builtin filter exists in both the index and detail
+ self.assertContains(response, '<h3 id="built_in-add">add</h3>')
+ self.assertContains(response, '<li><a href="#built_in-add">add</a></li>')
+
+except ImportError:
+ pass
+
+class ValidXHTMLTests(TestCase):
+ fixtures = ['admin-views-users.xml']
+ urlbit = 'admin'
+
+ def setUp(self):
+ self._context_processors = None
+ self._use_i18n, settings.USE_I18N = settings.USE_I18N, False
+ if 'django.core.context_processors.i18n' in settings.TEMPLATE_CONTEXT_PROCESSORS:
+ self._context_processors = settings.TEMPLATE_CONTEXT_PROCESSORS
+ cp = list(settings.TEMPLATE_CONTEXT_PROCESSORS)
+ cp.remove('django.core.context_processors.i18n')
+ settings.TEMPLATE_CONTEXT_PROCESSORS = tuple(cp)
+ # Force re-evaluation of the contex processor list
+ django.template.context._standard_context_processors = None
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+ if self._context_processors is not None:
+ settings.TEMPLATE_CONTEXT_PROCESSORS = self._context_processors
+ # Force re-evaluation of the contex processor list
+ django.template.context._standard_context_processors = None
+ settings.USE_I18N = self._use_i18n
+
+ def testLangNamePresent(self):
+ response = self.client.get('/test_admin/%s/admin_views/' % self.urlbit)
+ self.assertFalse(' lang=""' in response.content)
+ self.assertFalse(' xml:lang=""' in response.content)
diff --git a/parts/django/tests/regressiontests/admin_views/urls.py b/parts/django/tests/regressiontests/admin_views/urls.py
new file mode 100644
index 0000000..f3f1fbd
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+import views
+import customadmin
+
+urlpatterns = patterns('',
+ (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ (r'^admin/secure-view/$', views.secure_view),
+ (r'^admin/', include(admin.site.urls)),
+ (r'^admin2/', include(customadmin.site.urls)),
+)
diff --git a/parts/django/tests/regressiontests/admin_views/views.py b/parts/django/tests/regressiontests/admin_views/views.py
new file mode 100644
index 0000000..732b253
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_views/views.py
@@ -0,0 +1,6 @@
+from django.contrib.admin.views.decorators import staff_member_required
+from django.http import HttpResponse
+
+def secure_view(request):
+ return HttpResponse('%s' % request.POST)
+secure_view = staff_member_required(secure_view) \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_widgets/__init__.py b/parts/django/tests/regressiontests/admin_widgets/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_widgets/__init__.py
diff --git a/parts/django/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml b/parts/django/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml
new file mode 100644
index 0000000..b851562
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="101" model="auth.user">
+ <field type="CharField" name="username">testser</field>
+ <field type="CharField" name="first_name">Add</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">auser@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">False</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+
+ <object pk="1" model="admin_widgets.car">
+ <field to="auth.user" name="owner" rel="ManyToOneRel">100</field>
+ <field type="CharField" name="make">Volkswagon</field>
+ <field type="CharField" name="model">Passat</field>
+ </object>
+ <object pk="2" model="admin_widgets.car">
+ <field to="auth.user" name="owner" rel="ManyToOneRel">101</field>
+ <field type="CharField" name="make">BMW</field>
+ <field type="CharField" name="model">M3</field>
+ </object>
+
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/admin_widgets/models.py b/parts/django/tests/regressiontests/admin_widgets/models.py
new file mode 100644
index 0000000..450c3e6
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_widgets/models.py
@@ -0,0 +1,68 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+class MyFileField(models.FileField):
+ pass
+
+class Member(models.Model):
+ name = models.CharField(max_length=100)
+ birthdate = models.DateTimeField(blank=True, null=True)
+ gender = models.CharField(max_length=1, blank=True, choices=[('M','Male'), ('F', 'Female')])
+
+ def __unicode__(self):
+ return self.name
+
+class Band(models.Model):
+ name = models.CharField(max_length=100)
+ members = models.ManyToManyField(Member)
+
+ def __unicode__(self):
+ return self.name
+
+class Album(models.Model):
+ band = models.ForeignKey(Band)
+ name = models.CharField(max_length=100)
+ cover_art = models.FileField(upload_to='albums')
+ backside_art = MyFileField(upload_to='albums_back', null=True)
+
+ def __unicode__(self):
+ return self.name
+
+class HiddenInventoryManager(models.Manager):
+ def get_query_set(self):
+ return super(HiddenInventoryManager, self).get_query_set().filter(hidden=False)
+
+class Inventory(models.Model):
+ barcode = models.PositiveIntegerField(unique=True)
+ parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
+ name = models.CharField(blank=False, max_length=20)
+ hidden = models.BooleanField(default=False)
+
+ # see #9258
+ default_manager = models.Manager()
+ objects = HiddenInventoryManager()
+
+ def __unicode__(self):
+ return self.name
+
+class Event(models.Model):
+ band = models.ForeignKey(Band, limit_choices_to=models.Q(pk__gt=0))
+ start_date = models.DateField(blank=True, null=True)
+ start_time = models.TimeField(blank=True, null=True)
+ description = models.TextField(blank=True)
+ link = models.URLField(blank=True)
+ min_age = models.IntegerField(blank=True, null=True)
+
+class Car(models.Model):
+ owner = models.ForeignKey(User)
+ make = models.CharField(max_length=30)
+ model = models.CharField(max_length=30)
+
+ def __unicode__(self):
+ return u"%s %s" % (self.make, self.model)
+
+class CarTire(models.Model):
+ """
+ A single car tire. This to test that a user can only select their own cars.
+ """
+ car = models.ForeignKey(Car)
diff --git a/parts/django/tests/regressiontests/admin_widgets/tests.py b/parts/django/tests/regressiontests/admin_widgets/tests.py
new file mode 100644
index 0000000..51d883f
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_widgets/tests.py
@@ -0,0 +1,316 @@
+# encoding: utf-8
+
+from datetime import datetime
+from unittest import TestCase
+
+from django import forms
+from django.conf import settings
+from django.contrib import admin
+from django.contrib.admin import widgets
+from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime
+from django.contrib.admin.widgets import (AdminFileWidget, ForeignKeyRawIdWidget,
+ ManyToManyRawIdWidget)
+from django.core.files.storage import default_storage
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.db.models import DateField
+from django.test import TestCase as DjangoTestCase
+from django.utils.html import conditional_escape
+from django.utils.translation import activate, deactivate
+
+import models
+
+
+class AdminFormfieldForDBFieldTests(TestCase):
+ """
+ Tests for correct behavior of ModelAdmin.formfield_for_dbfield
+ """
+
+ def assertFormfield(self, model, fieldname, widgetclass, **admin_overrides):
+ """
+ Helper to call formfield_for_dbfield for a given model and field name
+ and verify that the returned formfield is appropriate.
+ """
+ # Override any settings on the model admin
+ class MyModelAdmin(admin.ModelAdmin): pass
+ for k in admin_overrides:
+ setattr(MyModelAdmin, k, admin_overrides[k])
+
+ # Construct the admin, and ask it for a formfield
+ ma = MyModelAdmin(model, admin.site)
+ ff = ma.formfield_for_dbfield(model._meta.get_field(fieldname), request=None)
+
+ # "unwrap" the widget wrapper, if needed
+ if isinstance(ff.widget, widgets.RelatedFieldWidgetWrapper):
+ widget = ff.widget.widget
+ else:
+ widget = ff.widget
+
+ # Check that we got a field of the right type
+ self.assert_(
+ isinstance(widget, widgetclass),
+ "Wrong widget for %s.%s: expected %s, got %s" % \
+ (model.__class__.__name__, fieldname, widgetclass, type(widget))
+ )
+
+ # Return the formfield so that other tests can continue
+ return ff
+
+ def testDateField(self):
+ self.assertFormfield(models.Event, 'start_date', widgets.AdminDateWidget)
+
+ def testDateTimeField(self):
+ self.assertFormfield(models.Member, 'birthdate', widgets.AdminSplitDateTime)
+
+ def testTimeField(self):
+ self.assertFormfield(models.Event, 'start_time', widgets.AdminTimeWidget)
+
+ def testTextField(self):
+ self.assertFormfield(models.Event, 'description', widgets.AdminTextareaWidget)
+
+ def testURLField(self):
+ self.assertFormfield(models.Event, 'link', widgets.AdminURLFieldWidget)
+
+ def testIntegerField(self):
+ self.assertFormfield(models.Event, 'min_age', widgets.AdminIntegerFieldWidget)
+
+ def testCharField(self):
+ self.assertFormfield(models.Member, 'name', widgets.AdminTextInputWidget)
+
+ def testFileField(self):
+ self.assertFormfield(models.Album, 'cover_art', widgets.AdminFileWidget)
+
+ def testForeignKey(self):
+ self.assertFormfield(models.Event, 'band', forms.Select)
+
+ def testRawIDForeignKey(self):
+ self.assertFormfield(models.Event, 'band', widgets.ForeignKeyRawIdWidget,
+ raw_id_fields=['band'])
+
+ def testRadioFieldsForeignKey(self):
+ ff = self.assertFormfield(models.Event, 'band', widgets.AdminRadioSelect,
+ radio_fields={'band':admin.VERTICAL})
+ self.assertEqual(ff.empty_label, None)
+
+ def testManyToMany(self):
+ self.assertFormfield(models.Band, 'members', forms.SelectMultiple)
+
+ def testRawIDManyTOMany(self):
+ self.assertFormfield(models.Band, 'members', widgets.ManyToManyRawIdWidget,
+ raw_id_fields=['members'])
+
+ def testFilteredManyToMany(self):
+ self.assertFormfield(models.Band, 'members', widgets.FilteredSelectMultiple,
+ filter_vertical=['members'])
+
+ def testFormfieldOverrides(self):
+ self.assertFormfield(models.Event, 'start_date', forms.TextInput,
+ formfield_overrides={DateField: {'widget': forms.TextInput}})
+
+ def testFieldWithChoices(self):
+ self.assertFormfield(models.Member, 'gender', forms.Select)
+
+ def testChoicesWithRadioFields(self):
+ self.assertFormfield(models.Member, 'gender', widgets.AdminRadioSelect,
+ radio_fields={'gender':admin.VERTICAL})
+
+ def testInheritance(self):
+ self.assertFormfield(models.Album, 'backside_art', widgets.AdminFileWidget)
+
+
+class AdminFormfieldForDBFieldWithRequestTests(DjangoTestCase):
+ fixtures = ["admin-widgets-users.xml"]
+
+ def testFilterChoicesByRequestUser(self):
+ """
+ Ensure the user can only see their own cars in the foreign key dropdown.
+ """
+ self.client.login(username="super", password="secret")
+ response = self.client.get("/widget_admin/admin_widgets/cartire/add/")
+ self.assert_("BMW M3" not in response.content)
+ self.assert_("Volkswagon Passat" in response.content)
+
+
+class AdminForeignKeyWidgetChangeList(DjangoTestCase):
+ fixtures = ["admin-widgets-users.xml"]
+ admin_root = '/widget_admin'
+
+ def setUp(self):
+ self.client.login(username="super", password="secret")
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_changelist_foreignkey(self):
+ response = self.client.get('%s/admin_widgets/car/' % self.admin_root)
+ self.assertTrue('%s/auth/user/add/' % self.admin_root in response.content)
+
+
+class AdminForeignKeyRawIdWidget(DjangoTestCase):
+ fixtures = ["admin-widgets-users.xml"]
+ admin_root = '/widget_admin'
+
+ def setUp(self):
+ self.client.login(username="super", password="secret")
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_nonexistent_target_id(self):
+ band = models.Band.objects.create(name='Bogey Blues')
+ pk = band.pk
+ band.delete()
+ post_data = {
+ "band": u'%s' % pk,
+ }
+ # Try posting with a non-existent pk in a raw id field: this
+ # should result in an error message, not a server exception.
+ response = self.client.post('%s/admin_widgets/event/add/' % self.admin_root,
+ post_data)
+ self.assertContains(response,
+ 'Select a valid choice. That choice is not one of the available choices.')
+
+ def test_invalid_target_id(self):
+
+ for test_str in ('Iñtërnâtiônàlizætiøn', "1234'", -1234):
+ # This should result in an error message, not a server exception.
+ response = self.client.post('%s/admin_widgets/event/add/' % self.admin_root,
+ {"band": test_str})
+
+ self.assertContains(response,
+ 'Select a valid choice. That choice is not one of the available choices.')
+
+
+class FilteredSelectMultipleWidgetTest(TestCase):
+ def test_render(self):
+ w = FilteredSelectMultiple('test', False)
+ self.assertEqual(
+ conditional_escape(w.render('test', 'test')),
+ '<select multiple="multiple" name="test" class="selectfilter">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 0, "%(ADMIN_MEDIA_PREFIX)s"); });</script>\n' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX}
+ )
+
+ def test_stacked_render(self):
+ w = FilteredSelectMultiple('test', True)
+ self.assertEqual(
+ conditional_escape(w.render('test', 'test')),
+ '<select multiple="multiple" name="test" class="selectfilterstacked">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 1, "%(ADMIN_MEDIA_PREFIX)s"); });</script>\n' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX}
+ )
+
+
+class AdminSplitDateTimeWidgetTest(TestCase):
+ def test_render(self):
+ w = AdminSplitDateTime()
+ self.assertEqual(
+ conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ '<p class="datetime">Date: <input value="2007-12-01" type="text" class="vDateField" name="test_0" size="10" /><br />Time: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>',
+ )
+
+ def test_localization(self):
+ w = AdminSplitDateTime()
+
+ activate('de-at')
+ old_USE_L10N = settings.USE_L10N
+ settings.USE_L10N = True
+ w.is_localized = True
+ self.assertEqual(
+ conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ '<p class="datetime">Datum: <input value="01.12.2007" type="text" class="vDateField" name="test_0" size="10" /><br />Zeit: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>',
+ )
+ deactivate()
+ settings.USE_L10N = old_USE_L10N
+
+
+class AdminFileWidgetTest(DjangoTestCase):
+ def test_render(self):
+ band = models.Band.objects.create(name='Linkin Park')
+ album = band.album_set.create(
+ name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg'
+ )
+
+ w = AdminFileWidget()
+ self.assertEqual(
+ conditional_escape(w.render('test', album.cover_art)),
+ 'Currently: <a target="_blank" href="%(STORAGE_URL)salbums/hybrid_theory.jpg">albums\hybrid_theory.jpg</a> <br />Change: <input type="file" name="test" />' % {'STORAGE_URL': default_storage.url('')},
+ )
+
+ self.assertEqual(
+ conditional_escape(w.render('test', SimpleUploadedFile('test', 'content'))),
+ '<input type="file" name="test" />',
+ )
+
+
+class ForeignKeyRawIdWidgetTest(DjangoTestCase):
+ def test_render(self):
+ band = models.Band.objects.create(name='Linkin Park')
+ band.album_set.create(
+ name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg'
+ )
+ rel = models.Album._meta.get_field('band').rel
+
+ w = ForeignKeyRawIdWidget(rel)
+ self.assertEqual(
+ conditional_escape(w.render('test', band.pk, attrs={})),
+ '<input type="text" name="test" value="%(bandpk)s" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Linkin Park</strong>' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX, "bandpk": band.pk},
+ )
+
+ def test_relations_to_non_primary_key(self):
+ # Check that ForeignKeyRawIdWidget works with fields which aren't
+ # related to the model's primary key.
+ apple = models.Inventory.objects.create(barcode=86, name='Apple')
+ models.Inventory.objects.create(barcode=22, name='Pear')
+ core = models.Inventory.objects.create(
+ barcode=87, name='Core', parent=apple
+ )
+ rel = models.Inventory._meta.get_field('parent').rel
+ w = ForeignKeyRawIdWidget(rel)
+ self.assertEqual(
+ w.render('test', core.parent_id, attrs={}),
+ '<input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Apple</strong>' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX},
+ )
+
+
+ def test_proper_manager_for_label_lookup(self):
+ # see #9258
+ rel = models.Inventory._meta.get_field('parent').rel
+ w = ForeignKeyRawIdWidget(rel)
+
+ hidden = models.Inventory.objects.create(
+ barcode=93, name='Hidden', hidden=True
+ )
+ child_of_hidden = models.Inventory.objects.create(
+ barcode=94, name='Child of hidden', parent=hidden
+ )
+ self.assertEqual(
+ w.render('test', child_of_hidden.parent_id, attrs={}),
+ '<input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Hidden</strong>' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX},
+ )
+
+
+class ManyToManyRawIdWidgetTest(DjangoTestCase):
+ def test_render(self):
+ band = models.Band.objects.create(name='Linkin Park')
+ band.album_set.create(
+ name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg'
+ )
+
+ m1 = models.Member.objects.create(name='Chester')
+ m2 = models.Member.objects.create(name='Mike')
+ band.members.add(m1, m2)
+ rel = models.Band._meta.get_field('members').rel
+
+ w = ManyToManyRawIdWidget(rel)
+ self.assertEqual(
+ conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})),
+ '<input type="text" name="test" value="%(m1pk)s,%(m2pk)s" class="vManyToManyRawIdAdminField" /><a href="../../../admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX, "m1pk": m1.pk, "m2pk": m2.pk},
+ )
+
+ self.assertEqual(
+ conditional_escape(w.render('test', [m1.pk])),
+ '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="../../../admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX, "m1pk": m1.pk},
+ )
+
+ self.assertEqual(w._has_changed(None, None), False)
+ self.assertEqual(w._has_changed([], None), False)
+ self.assertEqual(w._has_changed(None, [u'1']), True)
+ self.assertEqual(w._has_changed([1, 2], [u'1', u'2']), False)
+ self.assertEqual(w._has_changed([1, 2], [u'1']), True)
+ self.assertEqual(w._has_changed([1, 2], [u'1', u'3']), True)
diff --git a/parts/django/tests/regressiontests/admin_widgets/urls.py b/parts/django/tests/regressiontests/admin_widgets/urls.py
new file mode 100644
index 0000000..af73d53
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_widgets/urls.py
@@ -0,0 +1,7 @@
+
+from django.conf.urls.defaults import *
+import widgetadmin
+
+urlpatterns = patterns('',
+ (r'^', include(widgetadmin.site.urls)),
+)
diff --git a/parts/django/tests/regressiontests/admin_widgets/widgetadmin.py b/parts/django/tests/regressiontests/admin_widgets/widgetadmin.py
new file mode 100644
index 0000000..6f15d92
--- /dev/null
+++ b/parts/django/tests/regressiontests/admin_widgets/widgetadmin.py
@@ -0,0 +1,30 @@
+"""
+
+"""
+from django.contrib import admin
+
+import models
+
+class WidgetAdmin(admin.AdminSite):
+ pass
+
+class CarAdmin(admin.ModelAdmin):
+ list_display = ['make', 'model', 'owner']
+ list_editable = ['owner']
+
+class CarTireAdmin(admin.ModelAdmin):
+ def formfield_for_foreignkey(self, db_field, request, **kwargs):
+ if db_field.name == "car":
+ kwargs["queryset"] = models.Car.objects.filter(owner=request.user)
+ return db_field.formfield(**kwargs)
+ return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
+
+class EventAdmin(admin.ModelAdmin):
+ raw_id_fields = ['band']
+
+site = WidgetAdmin(name='widget-admin')
+
+site.register(models.User)
+site.register(models.Car, CarAdmin)
+site.register(models.CarTire, CarTireAdmin)
+site.register(models.Event, EventAdmin)
diff --git a/parts/django/tests/regressiontests/aggregation_regress/__init__.py b/parts/django/tests/regressiontests/aggregation_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/aggregation_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/aggregation_regress/fixtures/initial_data.json b/parts/django/tests/regressiontests/aggregation_regress/fixtures/initial_data.json
new file mode 100644
index 0000000..597ac04
--- /dev/null
+++ b/parts/django/tests/regressiontests/aggregation_regress/fixtures/initial_data.json
@@ -0,0 +1,257 @@
+[
+ {
+ "pk": 1,
+ "model": "aggregation_regress.publisher",
+ "fields": {
+ "name": "Apress",
+ "num_awards": 3
+ }
+ },
+ {
+ "pk": 2,
+ "model": "aggregation_regress.publisher",
+ "fields": {
+ "name": "Sams",
+ "num_awards": 1
+ }
+ },
+ {
+ "pk": 3,
+ "model": "aggregation_regress.publisher",
+ "fields": {
+ "name": "Prentice Hall",
+ "num_awards": 7
+ }
+ },
+ {
+ "pk": 4,
+ "model": "aggregation_regress.publisher",
+ "fields": {
+ "name": "Morgan Kaufmann",
+ "num_awards": 9
+ }
+ },
+ {
+ "pk": 5,
+ "model": "aggregation_regress.publisher",
+ "fields": {
+ "name": "Jonno's House of Books",
+ "num_awards": 0
+ }
+ },
+ {
+ "pk": 1,
+ "model": "aggregation_regress.book",
+ "fields": {
+ "publisher": 1,
+ "isbn": "159059725",
+ "name": "The Definitive Guide to Django: Web Development Done Right",
+ "price": "30.00",
+ "rating": 4.5,
+ "authors": [1, 2],
+ "contact": 1,
+ "pages": 447,
+ "pubdate": "2007-12-6"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "aggregation_regress.book",
+ "fields": {
+ "publisher": 2,
+ "isbn": "067232959",
+ "name": "Sams Teach Yourself Django in 24 Hours",
+ "price": "23.09",
+ "rating": 3.0,
+ "authors": [3],
+ "contact": 3,
+ "pages": 528,
+ "pubdate": "2008-3-3"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "aggregation_regress.book",
+ "fields": {
+ "publisher": 1,
+ "isbn": "159059996",
+ "name": "Practical Django Projects",
+ "price": "29.69",
+ "rating": 4.0,
+ "authors": [4],
+ "contact": 4,
+ "pages": 300,
+ "pubdate": "2008-6-23"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "aggregation_regress.book",
+ "fields": {
+ "publisher": 3,
+ "isbn": "013235613",
+ "name": "Python Web Development with Django",
+ "price": "29.69",
+ "rating": 4.0,
+ "authors": [5, 6, 7],
+ "contact": 5,
+ "pages": 350,
+ "pubdate": "2008-11-3"
+ }
+ },
+ {
+ "pk": 5,
+ "model": "aggregation_regress.book",
+ "fields": {
+ "publisher": 3,
+ "isbn": "013790395",
+ "name": "Artificial Intelligence: A Modern Approach",
+ "price": "82.80",
+ "rating": 4.0,
+ "authors": [8, 9],
+ "contact": 8,
+ "pages": 1132,
+ "pubdate": "1995-1-15"
+ }
+ },
+ {
+ "pk": 6,
+ "model": "aggregation_regress.book",
+ "fields": {
+ "publisher": 4,
+ "isbn": "155860191",
+ "name": "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp",
+ "price": "75.00",
+ "rating": 5.0,
+ "authors": [8],
+ "contact": 8,
+ "pages": 946,
+ "pubdate": "1991-10-15"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "aggregation_regress.store",
+ "fields": {
+ "books": [1, 2, 3, 4, 5, 6],
+ "name": "Amazon.com",
+ "original_opening": "1994-4-23 9:17:42",
+ "friday_night_closing": "23:59:59"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "aggregation_regress.store",
+ "fields": {
+ "books": [1, 3, 5, 6],
+ "name": "Books.com",
+ "original_opening": "2001-3-15 11:23:37",
+ "friday_night_closing": "23:59:59"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "aggregation_regress.store",
+ "fields": {
+ "books": [3, 4, 6],
+ "name": "Mamma and Pappa's Books",
+ "original_opening": "1945-4-25 16:24:14",
+ "friday_night_closing": "21:30:00"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 34,
+ "friends": [2, 4],
+ "name": "Adrian Holovaty"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 35,
+ "friends": [1, 7],
+ "name": "Jacob Kaplan-Moss"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 45,
+ "friends": [],
+ "name": "Brad Dayley"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 29,
+ "friends": [1],
+ "name": "James Bennett"
+ }
+ },
+ {
+ "pk": 5,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 37,
+ "friends": [6, 7],
+ "name": "Jeffrey Forcier"
+ }
+ },
+ {
+ "pk": 6,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 29,
+ "friends": [5, 7],
+ "name": "Paul Bissex"
+ }
+ },
+ {
+ "pk": 7,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 25,
+ "friends": [2, 5, 6],
+ "name": "Wesley J. Chun"
+ }
+ },
+ {
+ "pk": 8,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 57,
+ "friends": [9],
+ "name": "Peter Norvig"
+ }
+ },
+ {
+ "pk": 9,
+ "model": "aggregation_regress.author",
+ "fields": {
+ "age": 46,
+ "friends": [8],
+ "name": "Stuart Russell"
+ }
+ },
+ {
+ "pk": 5,
+ "model": "aggregation_regress.hardbackbook",
+ "fields": {
+ "weight": 4.5
+ }
+ },
+ {
+ "pk": 6,
+ "model": "aggregation_regress.hardbackbook",
+ "fields": {
+ "weight": 3.7
+ }
+ }
+]
diff --git a/parts/django/tests/regressiontests/aggregation_regress/models.py b/parts/django/tests/regressiontests/aggregation_regress/models.py
new file mode 100644
index 0000000..ccef9a5
--- /dev/null
+++ b/parts/django/tests/regressiontests/aggregation_regress/models.py
@@ -0,0 +1,65 @@
+# coding: utf-8
+from django.db import models
+
+
+class Author(models.Model):
+ name = models.CharField(max_length=100)
+ age = models.IntegerField()
+ friends = models.ManyToManyField('self', blank=True)
+
+ def __unicode__(self):
+ return self.name
+
+
+class Publisher(models.Model):
+ name = models.CharField(max_length=255)
+ num_awards = models.IntegerField()
+
+ def __unicode__(self):
+ return self.name
+
+
+class Book(models.Model):
+ isbn = models.CharField(max_length=9)
+ name = models.CharField(max_length=255)
+ pages = models.IntegerField()
+ rating = models.FloatField()
+ price = models.DecimalField(decimal_places=2, max_digits=6)
+ authors = models.ManyToManyField(Author)
+ contact = models.ForeignKey(Author, related_name='book_contact_set')
+ publisher = models.ForeignKey(Publisher)
+ pubdate = models.DateField()
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+
+class Store(models.Model):
+ name = models.CharField(max_length=255)
+ books = models.ManyToManyField(Book)
+ original_opening = models.DateTimeField()
+ friday_night_closing = models.TimeField()
+
+ def __unicode__(self):
+ return self.name
+
+class Entries(models.Model):
+ EntryID = models.AutoField(primary_key=True, db_column='Entry ID')
+ Entry = models.CharField(unique=True, max_length=50)
+ Exclude = models.BooleanField()
+
+
+class Clues(models.Model):
+ ID = models.AutoField(primary_key=True)
+ EntryID = models.ForeignKey(Entries, verbose_name='Entry', db_column = 'Entry ID')
+ Clue = models.CharField(max_length=150)
+
+
+class HardbackBook(Book):
+ weight = models.FloatField()
+
+ def __unicode__(self):
+ return "%s (hardback): %s" % (self.name, self.weight)
diff --git a/parts/django/tests/regressiontests/aggregation_regress/tests.py b/parts/django/tests/regressiontests/aggregation_regress/tests.py
new file mode 100644
index 0000000..4d5a2a0
--- /dev/null
+++ b/parts/django/tests/regressiontests/aggregation_regress/tests.py
@@ -0,0 +1,757 @@
+import datetime
+import pickle
+from decimal import Decimal
+from operator import attrgetter
+
+from django.conf import settings
+from django.core.exceptions import FieldError
+from django.db import DEFAULT_DB_ALIAS
+from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F
+from django.test import TestCase, Approximate
+
+from models import Author, Book, Publisher, Clues, Entries, HardbackBook
+
+
+def run_stddev_tests():
+ """Check to see if StdDev/Variance tests should be run.
+
+ Stddev and Variance are not guaranteed to be available for SQLite, and
+ are not available for PostgreSQL before 8.2.
+ """
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3':
+ return False
+
+ class StdDevPop(object):
+ sql_function = 'STDDEV_POP'
+
+ try:
+ connection.ops.check_aggregate_support(StdDevPop())
+ except:
+ return False
+ return True
+
+
+class AggregationTests(TestCase):
+ def assertObjectAttrs(self, obj, **kwargs):
+ for attr, value in kwargs.iteritems():
+ self.assertEqual(getattr(obj, attr), value)
+
+ def test_aggregates_in_where_clause(self):
+ """
+ Regression test for #12822: DatabaseError: aggregates not allowed in
+ WHERE clause
+
+ Tests that the subselect works and returns results equivalent to a
+ query with the IDs listed.
+
+ Before the corresponding fix for this bug, this test passed in 1.1 and
+ failed in 1.2-beta (trunk).
+ """
+ qs = Book.objects.values('contact').annotate(Max('id'))
+ qs = qs.order_by('contact').values_list('id__max', flat=True)
+ # don't do anything with the queryset (qs) before including it as a
+ # subquery
+ books = Book.objects.order_by('id')
+ qs1 = books.filter(id__in=qs)
+ qs2 = books.filter(id__in=list(qs))
+ self.assertEqual(list(qs1), list(qs2))
+
+ def test_aggregates_in_where_clause_pre_eval(self):
+ """
+ Regression test for #12822: DatabaseError: aggregates not allowed in
+ WHERE clause
+
+ Same as the above test, but evaluates the queryset for the subquery
+ before it's used as a subquery.
+
+ Before the corresponding fix for this bug, this test failed in both
+ 1.1 and 1.2-beta (trunk).
+ """
+ qs = Book.objects.values('contact').annotate(Max('id'))
+ qs = qs.order_by('contact').values_list('id__max', flat=True)
+ # force the queryset (qs) for the subquery to be evaluated in its
+ # current state
+ list(qs)
+ books = Book.objects.order_by('id')
+ qs1 = books.filter(id__in=qs)
+ qs2 = books.filter(id__in=list(qs))
+ self.assertEqual(list(qs1), list(qs2))
+
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
+ def test_annotate_with_extra(self):
+ """
+ Regression test for #11916: Extra params + aggregation creates
+ incorrect SQL.
+ """
+ #oracle doesn't support subqueries in group by clause
+ shortest_book_sql = """
+ SELECT name
+ FROM aggregation_regress_book b
+ WHERE b.publisher_id = aggregation_regress_publisher.id
+ ORDER BY b.pages
+ LIMIT 1
+ """
+ # tests that this query does not raise a DatabaseError due to the full
+ # subselect being (erroneously) added to the GROUP BY parameters
+ qs = Publisher.objects.extra(select={
+ 'name_of_shortest_book': shortest_book_sql,
+ }).annotate(total_books=Count('book'))
+ # force execution of the query
+ list(qs)
+
+ def test_aggregate(self):
+ # Ordering requests are ignored
+ self.assertEqual(
+ Author.objects.order_by("name").aggregate(Avg("age")),
+ {"age__avg": Approximate(37.444, places=1)}
+ )
+
+ # Implicit ordering is also ignored
+ self.assertEqual(
+ Book.objects.aggregate(Sum("pages")),
+ {"pages__sum": 3703},
+ )
+
+ # Baseline results
+ self.assertEqual(
+ Book.objects.aggregate(Sum('pages'), Avg('pages')),
+ {'pages__sum': 3703, 'pages__avg': Approximate(617.166, places=2)}
+ )
+
+ # Empty values query doesn't affect grouping or results
+ self.assertEqual(
+ Book.objects.values().aggregate(Sum('pages'), Avg('pages')),
+ {'pages__sum': 3703, 'pages__avg': Approximate(617.166, places=2)}
+ )
+
+ # Aggregate overrides extra selected column
+ self.assertEqual(
+ Book.objects.extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages')),
+ {'pages__sum': 3703}
+ )
+
+ def test_annotation(self):
+ # Annotations get combined with extra select clauses
+ obj = Book.objects.annotate(mean_auth_age=Avg("authors__age")).extra(select={"manufacture_cost": "price * .5"}).get(pk=2)
+ self.assertObjectAttrs(obj,
+ contact_id=3,
+ id=2,
+ isbn=u'067232959',
+ mean_auth_age=45.0,
+ name='Sams Teach Yourself Django in 24 Hours',
+ pages=528,
+ price=Decimal("23.09"),
+ pubdate=datetime.date(2008, 3, 3),
+ publisher_id=2,
+ rating=3.0
+ )
+ # Different DB backends return different types for the extra select computation
+ self.assertTrue(obj.manufacture_cost == 11.545 or obj.manufacture_cost == Decimal('11.545'))
+
+ # Order of the annotate/extra in the query doesn't matter
+ obj = Book.objects.extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2)
+ self.assertObjectAttrs(obj,
+ contact_id=3,
+ id=2,
+ isbn=u'067232959',
+ mean_auth_age=45.0,
+ name=u'Sams Teach Yourself Django in 24 Hours',
+ pages=528,
+ price=Decimal("23.09"),
+ pubdate=datetime.date(2008, 3, 3),
+ publisher_id=2,
+ rating=3.0
+ )
+ # Different DB backends return different types for the extra select computation
+ self.assertTrue(obj.manufacture_cost == 11.545 or obj.manufacture_cost == Decimal('11.545'))
+
+ # Values queries can be combined with annotate and extra
+ obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2)
+ manufacture_cost = obj['manufacture_cost']
+ self.assertTrue(manufacture_cost == 11.545 or manufacture_cost == Decimal('11.545'))
+ del obj['manufacture_cost']
+ self.assertEqual(obj, {
+ "contact_id": 3,
+ "id": 2,
+ "isbn": u"067232959",
+ "mean_auth_age": 45.0,
+ "name": u"Sams Teach Yourself Django in 24 Hours",
+ "pages": 528,
+ "price": Decimal("23.09"),
+ "pubdate": datetime.date(2008, 3, 3),
+ "publisher_id": 2,
+ "rating": 3.0,
+ })
+
+ # The order of the (empty) values, annotate and extra clauses doesn't
+ # matter
+ obj = Book.objects.values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2)
+ manufacture_cost = obj['manufacture_cost']
+ self.assertTrue(manufacture_cost == 11.545 or manufacture_cost == Decimal('11.545'))
+ del obj['manufacture_cost']
+ self.assertEqual(obj, {
+ 'contact_id': 3,
+ 'id': 2,
+ 'isbn': u'067232959',
+ 'mean_auth_age': 45.0,
+ 'name': u'Sams Teach Yourself Django in 24 Hours',
+ 'pages': 528,
+ 'price': Decimal("23.09"),
+ 'pubdate': datetime.date(2008, 3, 3),
+ 'publisher_id': 2,
+ 'rating': 3.0
+ })
+
+ # If the annotation precedes the values clause, it won't be included
+ # unless it is explicitly named
+ obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1)
+ self.assertEqual(obj, {
+ "name": u'The Definitive Guide to Django: Web Development Done Right',
+ })
+
+ obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name','mean_auth_age').get(pk=1)
+ self.assertEqual(obj, {
+ 'mean_auth_age': 34.5,
+ 'name': u'The Definitive Guide to Django: Web Development Done Right',
+ })
+
+ # If an annotation isn't included in the values, it can still be used
+ # in a filter
+ qs = Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2)
+ self.assertQuerysetEqual(
+ qs, [
+ {"name": u'Python Web Development with Django'}
+ ],
+ lambda b: b,
+ )
+
+ # The annotations are added to values output if values() precedes
+ # annotate()
+ obj = Book.objects.values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1)
+ self.assertEqual(obj, {
+ 'mean_auth_age': 34.5,
+ 'name': u'The Definitive Guide to Django: Web Development Done Right',
+ })
+
+ # Check that all of the objects are getting counted (allow_nulls) and
+ # that values respects the amount of objects
+ self.assertEqual(
+ len(Author.objects.annotate(Avg('friends__age')).values()),
+ 9
+ )
+
+ # Check that consecutive calls to annotate accumulate in the query
+ qs = Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest', 'price').annotate(Max('publisher__num_awards'))
+ self.assertQuerysetEqual(
+ qs, [
+ {'price': Decimal("30"), 'oldest': 35, 'publisher__num_awards__max': 3},
+ {'price': Decimal("29.69"), 'oldest': 37, 'publisher__num_awards__max': 7},
+ {'price': Decimal("23.09"), 'oldest': 45, 'publisher__num_awards__max': 1},
+ {'price': Decimal("75"), 'oldest': 57, 'publisher__num_awards__max': 9},
+ {'price': Decimal("82.8"), 'oldest': 57, 'publisher__num_awards__max': 7}
+ ],
+ lambda b: b,
+ )
+
+ def test_aggrate_annotation(self):
+ # Aggregates can be composed over annotations.
+ # The return type is derived from the composed aggregate
+ vals = Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('pages'), Max('price'), Sum('num_authors'), Avg('num_authors'))
+ self.assertEqual(vals, {
+ 'num_authors__sum': 10,
+ 'num_authors__avg': Approximate(1.666, places=2),
+ 'pages__max': 1132,
+ 'price__max': Decimal("82.80")
+ })
+
+ def test_field_error(self):
+ # Bad field requests in aggregates are caught and reported
+ self.assertRaises(
+ FieldError,
+ lambda: Book.objects.all().aggregate(num_authors=Count('foo'))
+ )
+
+ self.assertRaises(
+ FieldError,
+ lambda: Book.objects.all().annotate(num_authors=Count('foo'))
+ )
+
+ self.assertRaises(
+ FieldError,
+ lambda: Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo'))
+ )
+
+ def test_more(self):
+ # Old-style count aggregations can be mixed with new-style
+ self.assertEqual(
+ Book.objects.annotate(num_authors=Count('authors')).count(),
+ 6
+ )
+
+ # Non-ordinal, non-computed Aggregates over annotations correctly
+ # inherit the annotation's internal type if the annotation is ordinal
+ # or computed
+ vals = Book.objects.annotate(num_authors=Count('authors')).aggregate(Max('num_authors'))
+ self.assertEqual(
+ vals,
+ {'num_authors__max': 3}
+ )
+
+ vals = Publisher.objects.annotate(avg_price=Avg('book__price')).aggregate(Max('avg_price'))
+ self.assertEqual(
+ vals,
+ {'avg_price__max': 75.0}
+ )
+
+ # Aliases are quoted to protected aliases that might be reserved names
+ vals = Book.objects.aggregate(number=Max('pages'), select=Max('pages'))
+ self.assertEqual(
+ vals,
+ {'number': 1132, 'select': 1132}
+ )
+
+ # Regression for #10064: select_related() plays nice with aggregates
+ obj = Book.objects.select_related('publisher').annotate(num_authors=Count('authors')).values()[0]
+ self.assertEqual(obj, {
+ 'contact_id': 8,
+ 'id': 5,
+ 'isbn': u'013790395',
+ 'name': u'Artificial Intelligence: A Modern Approach',
+ 'num_authors': 2,
+ 'pages': 1132,
+ 'price': Decimal("82.8"),
+ 'pubdate': datetime.date(1995, 1, 15),
+ 'publisher_id': 3,
+ 'rating': 4.0,
+ })
+
+ # Regression for #10010: exclude on an aggregate field is correctly
+ # negated
+ self.assertEqual(
+ len(Book.objects.annotate(num_authors=Count('authors'))),
+ 6
+ )
+ self.assertEqual(
+ len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=2)),
+ 1
+ )
+ self.assertEqual(
+ len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__gt=2)),
+ 5
+ )
+
+ self.assertEqual(
+ len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__lt=3).exclude(num_authors__lt=2)),
+ 2
+ )
+ self.assertEqual(
+ len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3)),
+ 2
+ )
+
+ def test_aggregate_fexpr(self):
+ # Aggregates can be used with F() expressions
+ # ... where the F() is pushed into the HAVING clause
+ qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
+ self.assertQuerysetEqual(
+ qs, [
+ {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9},
+ {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}
+ ],
+ lambda p: p,
+ )
+
+ qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
+ self.assertQuerysetEqual(
+ qs, [
+ {'num_books': 2, 'name': u'Apress', 'num_awards': 3},
+ {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0},
+ {'num_books': 1, 'name': u'Sams', 'num_awards': 1}
+ ],
+ lambda p: p,
+ )
+
+ # ... and where the F() references an aggregate
+ qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards')
+ self.assertQuerysetEqual(
+ qs, [
+ {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9},
+ {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}
+ ],
+ lambda p: p,
+ )
+
+ qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
+ self.assertQuerysetEqual(
+ qs, [
+ {'num_books': 2, 'name': u'Apress', 'num_awards': 3},
+ {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0},
+ {'num_books': 1, 'name': u'Sams', 'num_awards': 1}
+ ],
+ lambda p: p,
+ )
+
+ def test_db_col_table(self):
+ # Tests on fields with non-default table and column names.
+ qs = Clues.objects.values('EntryID__Entry').annotate(Appearances=Count('EntryID'), Distinct_Clues=Count('Clue', distinct=True))
+ self.assertQuerysetEqual(qs, [])
+
+ qs = Entries.objects.annotate(clue_count=Count('clues__ID'))
+ self.assertQuerysetEqual(qs, [])
+
+ def test_empty(self):
+ # Regression for #10089: Check handling of empty result sets with
+ # aggregates
+ self.assertEqual(
+ Book.objects.filter(id__in=[]).count(),
+ 0
+ )
+
+ vals = Book.objects.filter(id__in=[]).aggregate(num_authors=Count('authors'), avg_authors=Avg('authors'), max_authors=Max('authors'), max_price=Max('price'), max_rating=Max('rating'))
+ self.assertEqual(
+ vals,
+ {'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None}
+ )
+
+ qs = Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values()
+ self.assertQuerysetEqual(
+ qs, [
+ {'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None}
+ ],
+ lambda p: p
+ )
+
+ def test_more_more(self):
+ # Regression for #10113 - Fields mentioned in order_by() must be
+ # included in the GROUP BY. This only becomes a problem when the
+ # order_by introduces a new join.
+ self.assertQuerysetEqual(
+ Book.objects.annotate(num_authors=Count('authors')).order_by('publisher__name', 'name'), [
+ "Practical Django Projects",
+ "The Definitive Guide to Django: Web Development Done Right",
+ "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp",
+ "Artificial Intelligence: A Modern Approach",
+ "Python Web Development with Django",
+ "Sams Teach Yourself Django in 24 Hours",
+ ],
+ lambda b: b.name
+ )
+
+ # Regression for #10127 - Empty select_related() works with annotate
+ qs = Book.objects.filter(rating__lt=4.5).select_related().annotate(Avg('authors__age'))
+ self.assertQuerysetEqual(
+ qs, [
+ (u'Artificial Intelligence: A Modern Approach', 51.5, u'Prentice Hall', u'Peter Norvig'),
+ (u'Practical Django Projects', 29.0, u'Apress', u'James Bennett'),
+ (u'Python Web Development with Django', Approximate(30.333, places=2), u'Prentice Hall', u'Jeffrey Forcier'),
+ (u'Sams Teach Yourself Django in 24 Hours', 45.0, u'Sams', u'Brad Dayley')
+ ],
+ lambda b: (b.name, b.authors__age__avg, b.publisher.name, b.contact.name)
+ )
+
+ # Regression for #10132 - If the values() clause only mentioned extra
+ # (select=) columns, those columns are used for grouping
+ qs = Book.objects.extra(select={'pub':'publisher_id'}).values('pub').annotate(Count('id')).order_by('pub')
+ self.assertQuerysetEqual(
+ qs, [
+ {'pub': 1, 'id__count': 2},
+ {'pub': 2, 'id__count': 1},
+ {'pub': 3, 'id__count': 2},
+ {'pub': 4, 'id__count': 1}
+ ],
+ lambda b: b
+ )
+
+ qs = Book.objects.extra(select={'pub':'publisher_id', 'foo':'pages'}).values('pub').annotate(Count('id')).order_by('pub')
+ self.assertQuerysetEqual(
+ qs, [
+ {'pub': 1, 'id__count': 2},
+ {'pub': 2, 'id__count': 1},
+ {'pub': 3, 'id__count': 2},
+ {'pub': 4, 'id__count': 1}
+ ],
+ lambda b: b
+ )
+
+ # Regression for #10182 - Queries with aggregate calls are correctly
+ # realiased when used in a subquery
+ ids = Book.objects.filter(pages__gt=100).annotate(n_authors=Count('authors')).filter(n_authors__gt=2).order_by('n_authors')
+ self.assertQuerysetEqual(
+ Book.objects.filter(id__in=ids), [
+ "Python Web Development with Django",
+ ],
+ lambda b: b.name
+ )
+
+ def test_duplicate_alias(self):
+ # Regression for #11256 - duplicating a default alias raises ValueError.
+ self.assertRaises(ValueError, Book.objects.all().annotate, Avg('authors__age'), authors__age__avg=Avg('authors__age'))
+
+ def test_field_name_conflict(self):
+ # Regression for #11256 - providing an aggregate name that conflicts with a field name on the model raises ValueError
+ self.assertRaises(ValueError, Author.objects.annotate, age=Avg('friends__age'))
+
+ def test_m2m_name_conflict(self):
+ # Regression for #11256 - providing an aggregate name that conflicts with an m2m name on the model raises ValueError
+ self.assertRaises(ValueError, Author.objects.annotate, friends=Count('friends'))
+
+ def test_reverse_relation_name_conflict(self):
+ # Regression for #11256 - providing an aggregate name that conflicts with a reverse-related name on the model raises ValueError
+ self.assertRaises(ValueError, Author.objects.annotate, book_contact_set=Avg('friends__age'))
+
+ def test_pickle(self):
+ # Regression for #10197 -- Queries with aggregates can be pickled.
+ # First check that pickling is possible at all. No crash = success
+ qs = Book.objects.annotate(num_authors=Count('authors'))
+ pickle.dumps(qs)
+
+ # Then check that the round trip works.
+ query = qs.query.get_compiler(qs.db).as_sql()[0]
+ qs2 = pickle.loads(pickle.dumps(qs))
+ self.assertEqual(
+ qs2.query.get_compiler(qs2.db).as_sql()[0],
+ query,
+ )
+
+ def test_more_more_more(self):
+ # Regression for #10199 - Aggregate calls clone the original query so
+ # the original query can still be used
+ books = Book.objects.all()
+ books.aggregate(Avg("authors__age"))
+ self.assertQuerysetEqual(
+ books.all(), [
+ u'Artificial Intelligence: A Modern Approach',
+ u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp',
+ u'Practical Django Projects',
+ u'Python Web Development with Django',
+ u'Sams Teach Yourself Django in 24 Hours',
+ u'The Definitive Guide to Django: Web Development Done Right'
+ ],
+ lambda b: b.name
+ )
+
+ # Regression for #10248 - Annotations work with DateQuerySets
+ qs = Book.objects.annotate(num_authors=Count('authors')).filter(num_authors=2).dates('pubdate', 'day')
+ self.assertQuerysetEqual(
+ qs, [
+ datetime.datetime(1995, 1, 15, 0, 0),
+ datetime.datetime(2007, 12, 6, 0, 0)
+ ],
+ lambda b: b
+ )
+
+ # Regression for #10290 - extra selects with parameters can be used for
+ # grouping.
+ qs = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'sheets' : '(pages + %s) / %s'}, select_params=[1, 2]).order_by('sheets').values('sheets')
+ self.assertQuerysetEqual(
+ qs, [
+ 150,
+ 175,
+ 224,
+ 264,
+ 473,
+ 566
+ ],
+ lambda b: int(b["sheets"])
+ )
+
+ # Regression for 10425 - annotations don't get in the way of a count()
+ # clause
+ self.assertEqual(
+ Book.objects.values('publisher').annotate(Count('publisher')).count(),
+ 4
+ )
+ self.assertEqual(
+ Book.objects.annotate(Count('publisher')).values('publisher').count(),
+ 6
+ )
+
+ publishers = Publisher.objects.filter(id__in=[1, 2])
+ self.assertQuerysetEqual(
+ publishers, [
+ "Apress",
+ "Sams"
+ ],
+ lambda p: p.name
+ )
+
+ publishers = publishers.annotate(n_books=Count("book"))
+ self.assertEqual(
+ publishers[0].n_books,
+ 2
+ )
+
+ self.assertQuerysetEqual(
+ publishers, [
+ "Apress",
+ "Sams",
+ ],
+ lambda p: p.name
+ )
+
+ books = Book.objects.filter(publisher__in=publishers)
+ self.assertQuerysetEqual(
+ books, [
+ "Practical Django Projects",
+ "Sams Teach Yourself Django in 24 Hours",
+ "The Definitive Guide to Django: Web Development Done Right",
+ ],
+ lambda b: b.name
+ )
+ self.assertQuerysetEqual(
+ publishers, [
+ "Apress",
+ "Sams",
+ ],
+ lambda p: p.name
+ )
+
+ # Regression for 10666 - inherited fields work with annotations and
+ # aggregations
+ self.assertEqual(
+ HardbackBook.objects.aggregate(n_pages=Sum('book_ptr__pages')),
+ {'n_pages': 2078}
+ )
+
+ self.assertEqual(
+ HardbackBook.objects.aggregate(n_pages=Sum('pages')),
+ {'n_pages': 2078},
+ )
+
+ qs = HardbackBook.objects.annotate(n_authors=Count('book_ptr__authors')).values('name', 'n_authors')
+ self.assertQuerysetEqual(
+ qs, [
+ {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'},
+ {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}
+ ],
+ lambda h: h
+ )
+
+ qs = HardbackBook.objects.annotate(n_authors=Count('authors')).values('name', 'n_authors')
+ self.assertQuerysetEqual(
+ qs, [
+ {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'},
+ {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}
+ ],
+ lambda h: h,
+ )
+
+ # Regression for #10766 - Shouldn't be able to reference an aggregate
+ # fields in an an aggregate() call.
+ self.assertRaises(
+ FieldError,
+ lambda: Book.objects.annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age'))
+ )
+
+ def test_empty_filter_count(self):
+ self.assertEqual(
+ Author.objects.filter(id__in=[]).annotate(Count("friends")).count(),
+ 0
+ )
+
+ def test_empty_filter_aggregate(self):
+ self.assertEqual(
+ Author.objects.filter(id__in=[]).annotate(Count("friends")).aggregate(Count("pk")),
+ {"pk__count": None}
+ )
+
+ def test_annotate_and_join(self):
+ self.assertEqual(
+ Author.objects.annotate(c=Count("friends__name")).exclude(friends__name="Joe").count(),
+ Author.objects.count()
+ )
+
+ def test_f_expression_annotation(self):
+ # Books with less than 200 pages per author.
+ qs = Book.objects.values("name").annotate(
+ n_authors=Count("authors")
+ ).filter(
+ pages__lt=F("n_authors") * 200
+ ).values_list("pk")
+ self.assertQuerysetEqual(
+ Book.objects.filter(pk__in=qs), [
+ "Python Web Development with Django"
+ ],
+ attrgetter("name")
+ )
+
+ def test_values_annotate_values(self):
+ qs = Book.objects.values("name").annotate(
+ n_authors=Count("authors")
+ ).values_list("pk", flat=True)
+ self.assertEqual(list(qs), list(Book.objects.values_list("pk", flat=True)))
+
+ def test_having_group_by(self):
+ # Test that when a field occurs on the LHS of a HAVING clause that it
+ # appears correctly in the GROUP BY clause
+ qs = Book.objects.values_list("name").annotate(
+ n_authors=Count("authors")
+ ).filter(
+ pages__gt=F("n_authors")
+ ).values_list("name", flat=True)
+ # Results should be the same, all Books have more pages than authors
+ self.assertEqual(
+ list(qs), list(Book.objects.values_list("name", flat=True))
+ )
+
+ if run_stddev_tests():
+ def test_stddev(self):
+ self.assertEqual(
+ Book.objects.aggregate(StdDev('pages')),
+ {'pages__stddev': Approximate(311.46, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(StdDev('rating')),
+ {'rating__stddev': Approximate(0.60, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(StdDev('price')),
+ {'price__stddev': Approximate(24.16, 2)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(StdDev('pages', sample=True)),
+ {'pages__stddev': Approximate(341.19, 2)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(StdDev('rating', sample=True)),
+ {'rating__stddev': Approximate(0.66, 2)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(StdDev('price', sample=True)),
+ {'price__stddev': Approximate(26.46, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(Variance('pages')),
+ {'pages__variance': Approximate(97010.80, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(Variance('rating')),
+ {'rating__variance': Approximate(0.36, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(Variance('price')),
+ {'price__variance': Approximate(583.77, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(Variance('pages', sample=True)),
+ {'pages__variance': Approximate(116412.96, 1)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(Variance('rating', sample=True)),
+ {'rating__variance': Approximate(0.44, 2)}
+ )
+
+ self.assertEqual(
+ Book.objects.aggregate(Variance('price', sample=True)),
+ {'price__variance': Approximate(700.53, 2)}
+ )
diff --git a/parts/django/tests/regressiontests/app_loading/__init__.py b/parts/django/tests/regressiontests/app_loading/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/__init__.py
diff --git a/parts/django/tests/regressiontests/app_loading/eggs/brokenapp.egg b/parts/django/tests/regressiontests/app_loading/eggs/brokenapp.egg
new file mode 100755
index 0000000..8aca671
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/eggs/brokenapp.egg
Binary files differ
diff --git a/parts/django/tests/regressiontests/app_loading/eggs/modelapp.egg b/parts/django/tests/regressiontests/app_loading/eggs/modelapp.egg
new file mode 100755
index 0000000..c2370b5
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/eggs/modelapp.egg
Binary files differ
diff --git a/parts/django/tests/regressiontests/app_loading/eggs/nomodelapp.egg b/parts/django/tests/regressiontests/app_loading/eggs/nomodelapp.egg
new file mode 100755
index 0000000..5b8d217
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/eggs/nomodelapp.egg
Binary files differ
diff --git a/parts/django/tests/regressiontests/app_loading/eggs/omelet.egg b/parts/django/tests/regressiontests/app_loading/eggs/omelet.egg
new file mode 100755
index 0000000..bd1c687
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/eggs/omelet.egg
Binary files differ
diff --git a/parts/django/tests/regressiontests/app_loading/models.py b/parts/django/tests/regressiontests/app_loading/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/models.py
diff --git a/parts/django/tests/regressiontests/app_loading/parent/__init__.py b/parts/django/tests/regressiontests/app_loading/parent/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/parent/__init__.py
diff --git a/parts/django/tests/regressiontests/app_loading/parent/app/__init__.py b/parts/django/tests/regressiontests/app_loading/parent/app/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/parent/app/__init__.py
diff --git a/parts/django/tests/regressiontests/app_loading/parent/app1/__init__.py b/parts/django/tests/regressiontests/app_loading/parent/app1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/parent/app1/__init__.py
diff --git a/parts/django/tests/regressiontests/app_loading/parent/app_2/__init__.py b/parts/django/tests/regressiontests/app_loading/parent/app_2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/parent/app_2/__init__.py
diff --git a/parts/django/tests/regressiontests/app_loading/test_settings.py b/parts/django/tests/regressiontests/app_loading/test_settings.py
new file mode 100644
index 0000000..e956311
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/test_settings.py
@@ -0,0 +1,3 @@
+INSTALLED_APPS = (
+ 'parent.*',
+)
diff --git a/parts/django/tests/regressiontests/app_loading/tests.py b/parts/django/tests/regressiontests/app_loading/tests.py
new file mode 100644
index 0000000..4fb60b2
--- /dev/null
+++ b/parts/django/tests/regressiontests/app_loading/tests.py
@@ -0,0 +1,83 @@
+import copy
+import os
+import sys
+import time
+from unittest import TestCase
+
+from django.conf import Settings
+from django.db.models.loading import cache, load_app
+
+
+class InstalledAppsGlobbingTest(TestCase):
+ def setUp(self):
+ self.OLD_SYS_PATH = sys.path[:]
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+ self.OLD_TZ = os.environ.get("TZ")
+
+ def test_globbing(self):
+ settings = Settings('test_settings')
+ self.assertEquals(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
+
+ def tearDown(self):
+ sys.path = self.OLD_SYS_PATH
+ if hasattr(time, "tzset") and self.OLD_TZ:
+ os.environ["TZ"] = self.OLD_TZ
+ time.tzset()
+
+
+class EggLoadingTest(TestCase):
+
+ def setUp(self):
+ self.old_path = sys.path[:]
+ self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
+
+ # This test adds dummy applications to the app cache. These
+ # need to be removed in order to prevent bad interactions
+ # with the flush operation in other tests.
+ self.old_app_models = copy.deepcopy(cache.app_models)
+ self.old_app_store = copy.deepcopy(cache.app_store)
+
+ def tearDown(self):
+ sys.path = self.old_path
+ cache.app_models = self.old_app_models
+ cache.app_store = self.old_app_store
+
+ def test_egg1(self):
+ """Models module can be loaded from an app in an egg"""
+ egg_name = '%s/modelapp.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ models = load_app('app_with_models')
+ self.assertFalse(models is None)
+
+ def test_egg2(self):
+ """Loading an app from an egg that has no models returns no models (and no error)"""
+ egg_name = '%s/nomodelapp.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ models = load_app('app_no_models')
+ self.assertTrue(models is None)
+
+ def test_egg3(self):
+ """Models module can be loaded from an app located under an egg's top-level package"""
+ egg_name = '%s/omelet.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ models = load_app('omelet.app_with_models')
+ self.assertFalse(models is None)
+
+ def test_egg4(self):
+ """Loading an app with no models from under the top-level egg package generates no error"""
+ egg_name = '%s/omelet.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ models = load_app('omelet.app_no_models')
+ self.assertTrue(models is None)
+
+ def test_egg5(self):
+ """Loading an app from an egg that has an import error in its models module raises that error"""
+ egg_name = '%s/brokenapp.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ self.assertRaises(ImportError, load_app, 'broken_app')
+ try:
+ load_app('broken_app')
+ except ImportError, e:
+ # Make sure the message is indicating the actual
+ # problem in the broken app.
+ self.assertTrue("modelz" in e.args[0])
diff --git a/parts/django/tests/regressiontests/backends/__init__.py b/parts/django/tests/regressiontests/backends/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/backends/__init__.py
diff --git a/parts/django/tests/regressiontests/backends/models.py b/parts/django/tests/regressiontests/backends/models.py
new file mode 100644
index 0000000..ea7ff96
--- /dev/null
+++ b/parts/django/tests/regressiontests/backends/models.py
@@ -0,0 +1,37 @@
+from django.db import models
+
+class Square(models.Model):
+ root = models.IntegerField()
+ square = models.PositiveIntegerField()
+
+ def __unicode__(self):
+ return "%s ** 2 == %s" % (self.root, self.square)
+
+class Person(models.Model):
+ first_name = models.CharField(max_length=20)
+ last_name = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return u'%s %s' % (self.first_name, self.last_name)
+
+class SchoolClass(models.Model):
+ year = models.PositiveIntegerField()
+ day = models.CharField(max_length=9, blank=True)
+ last_updated = models.DateTimeField()
+
+
+class Reporter(models.Model):
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=30)
+
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
+
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100)
+ pub_date = models.DateField()
+ reporter = models.ForeignKey(Reporter)
+
+ def __unicode__(self):
+ return self.headline
diff --git a/parts/django/tests/regressiontests/backends/tests.py b/parts/django/tests/regressiontests/backends/tests.py
new file mode 100644
index 0000000..10cec89
--- /dev/null
+++ b/parts/django/tests/regressiontests/backends/tests.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# Unit and doctests for specific database backends.
+import datetime
+import unittest
+
+from django.conf import settings
+from django.db import backend, connection, DEFAULT_DB_ALIAS, IntegrityError
+from django.db.backends.signals import connection_created
+from django.db.backends.postgresql import version as pg_version
+from django.test import TestCase, TransactionTestCase
+
+import models
+
+class OracleChecks(unittest.TestCase):
+
+ def test_dbms_session(self):
+ # If the backend is Oracle, test that we can call a standard
+ # stored procedure through our cursor wrapper.
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+ convert_unicode = backend.convert_unicode
+ cursor = connection.cursor()
+ cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'),
+ [convert_unicode('_django_testing!'),])
+ return True
+ else:
+ return True
+
+ def test_cursor_var(self):
+ # If the backend is Oracle, test that we can pass cursor variables
+ # as query parameters.
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+ cursor = connection.cursor()
+ var = cursor.var(backend.Database.STRING)
+ cursor.execute("BEGIN %s := 'X'; END; ", [var])
+ self.assertEqual(var.getvalue(), 'X')
+
+ def test_long_string(self):
+ # If the backend is Oracle, test that we can save a text longer
+ # than 4000 chars and read it properly
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+ c = connection.cursor()
+ c.execute('CREATE TABLE ltext ("TEXT" NCLOB)')
+ long_str = ''.join([unicode(x) for x in xrange(4000)])
+ c.execute('INSERT INTO ltext VALUES (%s)',[long_str])
+ c.execute('SELECT text FROM ltext')
+ row = c.fetchone()
+ self.assertEquals(long_str, row[0].read())
+ c.execute('DROP TABLE ltext')
+
+ def test_client_encoding(self):
+ # If the backend is Oracle, test that the client encoding is set
+ # correctly. This was broken under Cygwin prior to r14781.
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+ c = connection.cursor() # Ensure the connection is initialized.
+ self.assertEqual(connection.connection.encoding, "UTF-8")
+ self.assertEqual(connection.connection.nencoding, "UTF-8")
+
+class DateQuotingTest(TestCase):
+
+ def test_django_date_trunc(self):
+ """
+ Test the custom ``django_date_trunc method``, in particular against
+ fields which clash with strings passed to it (e.g. 'year') - see
+ #12818__.
+
+ __: http://code.djangoproject.com/ticket/12818
+
+ """
+ updated = datetime.datetime(2010, 2, 20)
+ models.SchoolClass.objects.create(year=2009, last_updated=updated)
+ years = models.SchoolClass.objects.dates('last_updated', 'year')
+ self.assertEqual(list(years), [datetime.datetime(2010, 1, 1, 0, 0)])
+
+ def test_django_extract(self):
+ """
+ Test the custom ``django_extract method``, in particular against fields
+ which clash with strings passed to it (e.g. 'day') - see #12818__.
+
+ __: http://code.djangoproject.com/ticket/12818
+
+ """
+ updated = datetime.datetime(2010, 2, 20)
+ models.SchoolClass.objects.create(year=2009, last_updated=updated)
+ classes = models.SchoolClass.objects.filter(last_updated__day=20)
+ self.assertEqual(len(classes), 1)
+
+class ParameterHandlingTest(TestCase):
+ def test_bad_parameter_count(self):
+ "An executemany call with too many/not enough parameters will raise an exception (Refs #12612)"
+ cursor = connection.cursor()
+ query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' % (
+ connection.introspection.table_name_converter('backends_square'),
+ connection.ops.quote_name('root'),
+ connection.ops.quote_name('square')
+ ))
+ self.assertRaises(Exception, cursor.executemany, query, [(1,2,3),])
+ self.assertRaises(Exception, cursor.executemany, query, [(1,),])
+
+class PostgresVersionTest(TestCase):
+ def assert_parses(self, version_string, version):
+ self.assertEqual(pg_version._parse_version(version_string), version)
+
+ def test_parsing(self):
+ self.assert_parses("PostgreSQL 8.3 beta4", (8, 3, None))
+ self.assert_parses("PostgreSQL 8.3", (8, 3, None))
+ self.assert_parses("EnterpriseDB 8.3", (8, 3, None))
+ self.assert_parses("PostgreSQL 8.3.6", (8, 3, 6))
+ self.assert_parses("PostgreSQL 8.4beta1", (8, 4, None))
+ self.assert_parses("PostgreSQL 8.3.1 on i386-apple-darwin9.2.2, compiled by GCC i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5478)", (8, 3, 1))
+
+# Unfortunately with sqlite3 the in-memory test database cannot be
+# closed, and so it cannot be re-opened during testing, and so we
+# sadly disable this test for now.
+if settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"] != "django.db.backends.sqlite3":
+ class ConnectionCreatedSignalTest(TestCase):
+ def test_signal(self):
+ data = {}
+ def receiver(sender, connection, **kwargs):
+ data["connection"] = connection
+
+ connection_created.connect(receiver)
+ connection.close()
+ cursor = connection.cursor()
+ self.assertTrue(data["connection"] is connection)
+
+ connection_created.disconnect(receiver)
+ data.clear()
+ cursor = connection.cursor()
+ self.assertTrue(data == {})
+
+
+class BackendTestCase(TestCase):
+ def test_cursor_executemany(self):
+ #4896: Test cursor.executemany
+ cursor = connection.cursor()
+ qn = connection.ops.quote_name
+ opts = models.Square._meta
+ f1, f2 = opts.get_field('root'), opts.get_field('square')
+ query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)'
+ % (connection.introspection.table_name_converter(opts.db_table), qn(f1.column), qn(f2.column)))
+ cursor.executemany(query, [(i, i**2) for i in range(-5, 6)])
+ self.assertEqual(models.Square.objects.count(), 11)
+ for i in range(-5, 6):
+ square = models.Square.objects.get(root=i)
+ self.assertEqual(square.square, i**2)
+
+ #4765: executemany with params=[] does nothing
+ cursor.executemany(query, [])
+ self.assertEqual(models.Square.objects.count(), 11)
+
+
+# We don't make these tests conditional because that means we would need to
+# check and differentiate between:
+# * MySQL+InnoDB, MySQL+MYISAM (something we currently can't do).
+# * if sqlite3 (if/once we get #14204 fixed) has referential integrity turned
+# on or not, something that would be controlled by runtime support and user
+# preference.
+# verify if its type is django.database.db.IntegrityError.
+
+class FkConstraintsTests(TransactionTestCase):
+
+ def setUp(self):
+ # Create a Reporter.
+ self.r = models.Reporter.objects.create(first_name='John', last_name='Smith')
+
+ def test_integrity_checks_on_creation(self):
+ """
+ Try to create a model instance that violates a FK constraint. If it
+ fails it should fail with IntegrityError.
+ """
+ a = models.Article(headline="This is a test", pub_date=datetime.datetime(2005, 7, 27), reporter_id=30)
+ try:
+ a.save()
+ except IntegrityError:
+ pass
+
+ def test_integrity_checks_on_update(self):
+ """
+ Try to update a model instance introducing a FK constraint violation.
+ If it fails it should fail with IntegrityError.
+ """
+ # Create an Article.
+ models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
+ # Retrive it from the DB
+ a = models.Article.objects.get(headline="Test article")
+ a.reporter_id = 30
+ try:
+ a.save()
+ except IntegrityError:
+ pass
+ def test_unicode_fetches(self):
+ #6254: fetchone, fetchmany, fetchall return strings as unicode objects
+ qn = connection.ops.quote_name
+ models.Person(first_name="John", last_name="Doe").save()
+ models.Person(first_name="Jane", last_name="Doe").save()
+ models.Person(first_name="Mary", last_name="Agnelline").save()
+ models.Person(first_name="Peter", last_name="Parker").save()
+ models.Person(first_name="Clark", last_name="Kent").save()
+ opts2 = models.Person._meta
+ f3, f4 = opts2.get_field('first_name'), opts2.get_field('last_name')
+ query2 = ('SELECT %s, %s FROM %s ORDER BY %s'
+ % (qn(f3.column), qn(f4.column), connection.introspection.table_name_converter(opts2.db_table),
+ qn(f3.column)))
+ cursor = connection.cursor()
+ cursor.execute(query2)
+ self.assertEqual(cursor.fetchone(), (u'Clark', u'Kent'))
+ self.assertEqual(list(cursor.fetchmany(2)), [(u'Jane', u'Doe'), (u'John', u'Doe')])
+ self.assertEqual(list(cursor.fetchall()), [(u'Mary', u'Agnelline'), (u'Peter', u'Parker')])
diff --git a/parts/django/tests/regressiontests/bash_completion/__init__.py b/parts/django/tests/regressiontests/bash_completion/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/bash_completion/__init__.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/bash_completion/management/__init__.py b/parts/django/tests/regressiontests/bash_completion/management/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/bash_completion/management/__init__.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/bash_completion/management/commands/__init__.py b/parts/django/tests/regressiontests/bash_completion/management/commands/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/bash_completion/management/commands/__init__.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/bash_completion/management/commands/test_command.py b/parts/django/tests/regressiontests/bash_completion/management/commands/test_command.py
new file mode 100644
index 0000000..5cb8820
--- /dev/null
+++ b/parts/django/tests/regressiontests/bash_completion/management/commands/test_command.py
@@ -0,0 +1,14 @@
+import sys, os
+from optparse import OptionParser, make_option
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+ option_list = BaseCommand.option_list + (
+ make_option("--list", action="store_true", dest="list",
+ help="Print all options"),
+ )
+
+ def handle(self, *args, **options):
+ pass
diff --git a/parts/django/tests/regressiontests/bash_completion/models.py b/parts/django/tests/regressiontests/bash_completion/models.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/bash_completion/models.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/bash_completion/tests.py b/parts/django/tests/regressiontests/bash_completion/tests.py
new file mode 100644
index 0000000..24c8b1d
--- /dev/null
+++ b/parts/django/tests/regressiontests/bash_completion/tests.py
@@ -0,0 +1,87 @@
+"""
+A series of tests to establish that the command-line bash completion works.
+"""
+import os
+import unittest
+import sys
+import StringIO
+
+from django.conf import settings
+from django.core.management import ManagementUtility
+
+class BashCompletionTests(unittest.TestCase):
+ """
+ Testing the Python level bash completion code.
+ This requires setting up the environment as if we got passed data
+ from bash.
+ """
+
+ def setUp(self):
+ self.old_DJANGO_AUTO_COMPLETE = os.environ.get('DJANGO_AUTO_COMPLETE')
+ os.environ['DJANGO_AUTO_COMPLETE'] = '1'
+ self.output = StringIO.StringIO()
+ self.old_stdout = sys.stdout
+ sys.stdout = self.output
+
+ def tearDown(self):
+ sys.stdout = self.old_stdout
+ if self.old_DJANGO_AUTO_COMPLETE:
+ os.environ['DJANGO_AUTO_COMPLETE'] = self.old_DJANGO_AUTO_COMPLETE
+ else:
+ del os.environ['DJANGO_AUTO_COMPLETE']
+
+ def _user_input(self, input_str):
+ os.environ['COMP_WORDS'] = input_str
+ os.environ['COMP_CWORD'] = str(len(input_str.split()) - 1)
+ sys.argv = input_str.split(' ')
+
+ def _run_autocomplete(self):
+ util = ManagementUtility(argv=sys.argv)
+ try:
+ util.autocomplete()
+ except SystemExit:
+ pass
+ return self.output.getvalue().strip().split('\n')
+
+ def test_django_admin_py(self):
+ "django_admin.py will autocomplete option flags"
+ self._user_input('django-admin.py sqlall --v')
+ output = self._run_autocomplete()
+ self.assertEqual(output, ['--verbosity='])
+
+ def test_manage_py(self):
+ "manage.py will autocomplete option flags"
+ self._user_input('manage.py sqlall --v')
+ output = self._run_autocomplete()
+ self.assertEqual(output, ['--verbosity='])
+
+ def test_custom_command(self):
+ "A custom command can autocomplete option flags"
+ self._user_input('django-admin.py test_command --l')
+ output = self._run_autocomplete()
+ self.assertEqual(output, ['--list'])
+
+ def test_subcommands(self):
+ "Subcommands can be autocompleted"
+ self._user_input('django-admin.py sql')
+ output = self._run_autocomplete()
+ self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqlflush sqlindexes sqlinitialdata sqlreset sqlsequencereset'])
+
+ def test_help(self):
+ "No errors, just an empty list if there are no autocomplete options"
+ self._user_input('django-admin.py help --')
+ output = self._run_autocomplete()
+ self.assertEqual(output, [''])
+
+ def test_runfcgi(self):
+ "Command arguments will be autocompleted"
+ self._user_input('django-admin.py runfcgi h')
+ output = self._run_autocomplete()
+ self.assertEqual(output, ['host='])
+
+ def test_app_completion(self):
+ "Application names will be autocompleted for an AppCommand"
+ self._user_input('django-admin.py sqlall a')
+ output = self._run_autocomplete()
+ app_labels = [name.split('.')[-1] for name in settings.INSTALLED_APPS]
+ self.assertEqual(output, sorted(label for label in app_labels if label.startswith('a')))
diff --git a/parts/django/tests/regressiontests/bug639/__init__.py b/parts/django/tests/regressiontests/bug639/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug639/__init__.py
diff --git a/parts/django/tests/regressiontests/bug639/models.py b/parts/django/tests/regressiontests/bug639/models.py
new file mode 100644
index 0000000..b7e3880
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug639/models.py
@@ -0,0 +1,26 @@
+import tempfile
+
+from django.db import models
+from django.core.files.storage import FileSystemStorage
+from django.forms import ModelForm
+
+temp_storage_dir = tempfile.mkdtemp()
+temp_storage = FileSystemStorage(temp_storage_dir)
+
+class Photo(models.Model):
+ title = models.CharField(max_length=30)
+ image = models.FileField(storage=temp_storage, upload_to='tests')
+
+ # Support code for the tests; this keeps track of how many times save()
+ # gets called on each instance.
+ def __init__(self, *args, **kwargs):
+ super(Photo, self).__init__(*args, **kwargs)
+ self._savecount = 0
+
+ def save(self, force_insert=False, force_update=False):
+ super(Photo, self).save(force_insert, force_update)
+ self._savecount += 1
+
+class PhotoForm(ModelForm):
+ class Meta:
+ model = Photo
diff --git a/parts/django/tests/regressiontests/bug639/test.jpg b/parts/django/tests/regressiontests/bug639/test.jpg
new file mode 100644
index 0000000..391b57a
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug639/test.jpg
Binary files differ
diff --git a/parts/django/tests/regressiontests/bug639/tests.py b/parts/django/tests/regressiontests/bug639/tests.py
new file mode 100644
index 0000000..2cc3a8a
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug639/tests.py
@@ -0,0 +1,41 @@
+"""
+Tests for file field behavior, and specifically #639, in which Model.save()
+gets called *again* for each FileField. This test will fail if calling a
+ModelForm's save() method causes Model.save() to be called more than once.
+"""
+
+import os
+import shutil
+import unittest
+
+from django.core.files.uploadedfile import SimpleUploadedFile
+from regressiontests.bug639.models import Photo, PhotoForm, temp_storage_dir
+
+class Bug639Test(unittest.TestCase):
+
+ def testBug639(self):
+ """
+ Simulate a file upload and check how many times Model.save() gets
+ called.
+ """
+ # Grab an image for testing.
+ filename = os.path.join(os.path.dirname(__file__), "test.jpg")
+ img = open(filename, "rb").read()
+
+ # Fake a POST QueryDict and FILES MultiValueDict.
+ data = {'title': 'Testing'}
+ files = {"image": SimpleUploadedFile('test.jpg', img, 'image/jpeg')}
+
+ form = PhotoForm(data=data, files=files)
+ p = form.save()
+
+ # Check the savecount stored on the object (see the model).
+ self.assertEqual(p._savecount, 1)
+
+ def tearDown(self):
+ """
+ Make sure to delete the "uploaded" file to avoid clogging /tmp.
+ """
+ p = Photo.objects.get()
+ p.image.delete(save=False)
+ shutil.rmtree(temp_storage_dir)
diff --git a/parts/django/tests/regressiontests/bug8245/__init__.py b/parts/django/tests/regressiontests/bug8245/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug8245/__init__.py
diff --git a/parts/django/tests/regressiontests/bug8245/admin.py b/parts/django/tests/regressiontests/bug8245/admin.py
new file mode 100644
index 0000000..1812269
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug8245/admin.py
@@ -0,0 +1,7 @@
+from django.contrib import admin
+
+from models import Story
+
+
+admin.site.register(Story)
+raise Exception("Bad admin module")
diff --git a/parts/django/tests/regressiontests/bug8245/models.py b/parts/django/tests/regressiontests/bug8245/models.py
new file mode 100644
index 0000000..5643955
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug8245/models.py
@@ -0,0 +1,4 @@
+from django.db import models
+
+class Story(models.Model):
+ title = models.CharField(max_length=10)
diff --git a/parts/django/tests/regressiontests/bug8245/tests.py b/parts/django/tests/regressiontests/bug8245/tests.py
new file mode 100644
index 0000000..5aa4a94
--- /dev/null
+++ b/parts/django/tests/regressiontests/bug8245/tests.py
@@ -0,0 +1,29 @@
+from unittest import TestCase
+
+from django.contrib import admin
+
+
+class Bug8245Test(TestCase):
+ """
+ Test for bug #8245 - don't raise an AlreadyRegistered exception when using
+ autodiscover() and an admin.py module contains an error.
+ """
+ def test_bug_8245(self):
+ # The first time autodiscover is called, we should get our real error.
+ try:
+ admin.autodiscover()
+ except Exception, e:
+ self.assertEqual(str(e), "Bad admin module")
+ else:
+ self.fail(
+ 'autodiscover should have raised a "Bad admin module" error.')
+
+ # Calling autodiscover again should raise the very same error it did
+ # the first time, not an AlreadyRegistered error.
+ try:
+ admin.autodiscover()
+ except Exception, e:
+ self.assertEqual(str(e), "Bad admin module")
+ else:
+ self.fail(
+ 'autodiscover should have raised a "Bad admin module" error.')
diff --git a/parts/django/tests/regressiontests/builtin_server/__init__.py b/parts/django/tests/regressiontests/builtin_server/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/builtin_server/__init__.py
diff --git a/parts/django/tests/regressiontests/builtin_server/models.py b/parts/django/tests/regressiontests/builtin_server/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/builtin_server/models.py
diff --git a/parts/django/tests/regressiontests/builtin_server/tests.py b/parts/django/tests/regressiontests/builtin_server/tests.py
new file mode 100644
index 0000000..c3cfef1
--- /dev/null
+++ b/parts/django/tests/regressiontests/builtin_server/tests.py
@@ -0,0 +1,51 @@
+from unittest import TestCase
+from StringIO import StringIO
+from django.core.servers.basehttp import ServerHandler
+
+#
+# Tests for #9659: wsgi.file_wrapper in the builtin server.
+# We need to mock a couple of of handlers and keep track of what
+# gets called when using a couple kinds of WSGI apps.
+#
+
+class DummyHandler(object):
+ def log_request(*args, **kwargs):
+ pass
+
+class FileWrapperHandler(ServerHandler):
+ def __init__(self, *args, **kwargs):
+ ServerHandler.__init__(self, *args, **kwargs)
+ self.request_handler = DummyHandler()
+ self._used_sendfile = False
+
+ def sendfile(self):
+ self._used_sendfile = True
+ return True
+
+def wsgi_app(environ, start_response):
+ start_response('200 OK', [('Content-Type', 'text/plain')])
+ return ['Hello World!']
+
+def wsgi_app_file_wrapper(environ, start_response):
+ start_response('200 OK', [('Content-Type', 'text/plain')])
+ return environ['wsgi.file_wrapper'](StringIO('foo'))
+
+class WSGIFileWrapperTests(TestCase):
+ """
+ Test that the wsgi.file_wrapper works for the builting server.
+ """
+
+ def test_file_wrapper_uses_sendfile(self):
+ env = {'SERVER_PROTOCOL': 'HTTP/1.0'}
+ err = StringIO()
+ handler = FileWrapperHandler(None, StringIO(), err, env)
+ handler.run(wsgi_app_file_wrapper)
+ self.assert_(handler._used_sendfile)
+
+ def test_file_wrapper_no_sendfile(self):
+ env = {'SERVER_PROTOCOL': 'HTTP/1.0'}
+ err = StringIO()
+ handler = FileWrapperHandler(None, StringIO(), err, env)
+ handler.run(wsgi_app)
+ self.assertFalse(handler._used_sendfile)
+ self.assertEqual(handler.stdout.getvalue().splitlines()[-1],'Hello World!')
diff --git a/parts/django/tests/regressiontests/cache/__init__.py b/parts/django/tests/regressiontests/cache/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/cache/__init__.py
diff --git a/parts/django/tests/regressiontests/cache/liberal_backend.py b/parts/django/tests/regressiontests/cache/liberal_backend.py
new file mode 100644
index 0000000..5c7e312
--- /dev/null
+++ b/parts/django/tests/regressiontests/cache/liberal_backend.py
@@ -0,0 +1,9 @@
+from django.core.cache.backends.locmem import CacheClass as LocMemCacheClass
+
+class LiberalKeyValidationMixin(object):
+ def validate_key(self, key):
+ pass
+
+class CacheClass(LiberalKeyValidationMixin, LocMemCacheClass):
+ pass
+
diff --git a/parts/django/tests/regressiontests/cache/models.py b/parts/django/tests/regressiontests/cache/models.py
new file mode 100644
index 0000000..643fd22
--- /dev/null
+++ b/parts/django/tests/regressiontests/cache/models.py
@@ -0,0 +1,11 @@
+from django.db import models
+from datetime import datetime
+
+def expensive_calculation():
+ expensive_calculation.num_runs += 1
+ return datetime.now()
+
+class Poll(models.Model):
+ question = models.CharField(max_length=200)
+ answer = models.CharField(max_length=200)
+ pub_date = models.DateTimeField('date published', default=expensive_calculation)
diff --git a/parts/django/tests/regressiontests/cache/tests.py b/parts/django/tests/regressiontests/cache/tests.py
new file mode 100644
index 0000000..7167e2f
--- /dev/null
+++ b/parts/django/tests/regressiontests/cache/tests.py
@@ -0,0 +1,652 @@
+# -*- coding: utf-8 -*-
+
+# Unit tests for cache framework
+# Uses whatever cache backend is set in the test settings file.
+
+import os
+import shutil
+import tempfile
+import time
+import unittest
+import warnings
+
+from django.conf import settings
+from django.core import management
+from django.core.cache import get_cache
+from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
+from django.http import HttpResponse, HttpRequest
+from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware
+from django.test.utils import get_warnings_state, restore_warnings_state
+from django.utils import translation
+from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
+from django.utils.hashcompat import md5_constructor
+from regressiontests.cache.models import Poll, expensive_calculation
+
+# functions/classes for complex data type tests
+def f():
+ return 42
+class C:
+ def m(n):
+ return 24
+
+class DummyCacheTests(unittest.TestCase):
+ # The Dummy cache backend doesn't really behave like a test backend,
+ # so it has different test requirements.
+ def setUp(self):
+ self.cache = get_cache('dummy://')
+
+ def test_simple(self):
+ "Dummy cache backend ignores cache set calls"
+ self.cache.set("key", "value")
+ self.assertEqual(self.cache.get("key"), None)
+
+ def test_add(self):
+ "Add doesn't do anything in dummy cache backend"
+ self.cache.add("addkey1", "value")
+ result = self.cache.add("addkey1", "newvalue")
+ self.assertEqual(result, True)
+ self.assertEqual(self.cache.get("addkey1"), None)
+
+ def test_non_existent(self):
+ "Non-existent keys aren't found in the dummy cache backend"
+ self.assertEqual(self.cache.get("does_not_exist"), None)
+ self.assertEqual(self.cache.get("does_not_exist", "bang!"), "bang!")
+
+ def test_get_many(self):
+ "get_many returns nothing for the dummy cache backend"
+ self.cache.set('a', 'a')
+ self.cache.set('b', 'b')
+ self.cache.set('c', 'c')
+ self.cache.set('d', 'd')
+ self.assertEqual(self.cache.get_many(['a', 'c', 'd']), {})
+ self.assertEqual(self.cache.get_many(['a', 'b', 'e']), {})
+
+ def test_delete(self):
+ "Cache deletion is transparently ignored on the dummy cache backend"
+ self.cache.set("key1", "spam")
+ self.cache.set("key2", "eggs")
+ self.assertEqual(self.cache.get("key1"), None)
+ self.cache.delete("key1")
+ self.assertEqual(self.cache.get("key1"), None)
+ self.assertEqual(self.cache.get("key2"), None)
+
+ def test_has_key(self):
+ "The has_key method doesn't ever return True for the dummy cache backend"
+ self.cache.set("hello1", "goodbye1")
+ self.assertEqual(self.cache.has_key("hello1"), False)
+ self.assertEqual(self.cache.has_key("goodbye1"), False)
+
+ def test_in(self):
+ "The in operator doesn't ever return True for the dummy cache backend"
+ self.cache.set("hello2", "goodbye2")
+ self.assertEqual("hello2" in self.cache, False)
+ self.assertEqual("goodbye2" in self.cache, False)
+
+ def test_incr(self):
+ "Dummy cache values can't be incremented"
+ self.cache.set('answer', 42)
+ self.assertRaises(ValueError, self.cache.incr, 'answer')
+ self.assertRaises(ValueError, self.cache.incr, 'does_not_exist')
+
+ def test_decr(self):
+ "Dummy cache values can't be decremented"
+ self.cache.set('answer', 42)
+ self.assertRaises(ValueError, self.cache.decr, 'answer')
+ self.assertRaises(ValueError, self.cache.decr, 'does_not_exist')
+
+ def test_data_types(self):
+ "All data types are ignored equally by the dummy cache"
+ stuff = {
+ 'string' : 'this is a string',
+ 'int' : 42,
+ 'list' : [1, 2, 3, 4],
+ 'tuple' : (1, 2, 3, 4),
+ 'dict' : {'A': 1, 'B' : 2},
+ 'function' : f,
+ 'class' : C,
+ }
+ self.cache.set("stuff", stuff)
+ self.assertEqual(self.cache.get("stuff"), None)
+
+ def test_expiration(self):
+ "Expiration has no effect on the dummy cache"
+ self.cache.set('expire1', 'very quickly', 1)
+ self.cache.set('expire2', 'very quickly', 1)
+ self.cache.set('expire3', 'very quickly', 1)
+
+ time.sleep(2)
+ self.assertEqual(self.cache.get("expire1"), None)
+
+ self.cache.add("expire2", "newvalue")
+ self.assertEqual(self.cache.get("expire2"), None)
+ self.assertEqual(self.cache.has_key("expire3"), False)
+
+ def test_unicode(self):
+ "Unicode values are ignored by the dummy cache"
+ stuff = {
+ u'ascii': u'ascii_value',
+ u'unicode_ascii': u'Iñtërnâtiônàlizætiøn1',
+ u'Iñtërnâtiônàlizætiøn': u'Iñtërnâtiônàlizætiøn2',
+ u'ascii': {u'x' : 1 }
+ }
+ for (key, value) in stuff.items():
+ self.cache.set(key, value)
+ self.assertEqual(self.cache.get(key), None)
+
+ def test_set_many(self):
+ "set_many does nothing for the dummy cache backend"
+ self.cache.set_many({'a': 1, 'b': 2})
+
+ def test_delete_many(self):
+ "delete_many does nothing for the dummy cache backend"
+ self.cache.delete_many(['a', 'b'])
+
+ def test_clear(self):
+ "clear does nothing for the dummy cache backend"
+ self.cache.clear()
+
+
+class BaseCacheTests(object):
+ # A common set of tests to apply to all cache backends
+ def tearDown(self):
+ self.cache.clear()
+
+ def test_simple(self):
+ # Simple cache set/get works
+ self.cache.set("key", "value")
+ self.assertEqual(self.cache.get("key"), "value")
+
+ def test_add(self):
+ # A key can be added to a cache
+ self.cache.add("addkey1", "value")
+ result = self.cache.add("addkey1", "newvalue")
+ self.assertEqual(result, False)
+ self.assertEqual(self.cache.get("addkey1"), "value")
+
+ def test_non_existent(self):
+ # Non-existent cache keys return as None/default
+ # get with non-existent keys
+ self.assertEqual(self.cache.get("does_not_exist"), None)
+ self.assertEqual(self.cache.get("does_not_exist", "bang!"), "bang!")
+
+ def test_get_many(self):
+ # Multiple cache keys can be returned using get_many
+ self.cache.set('a', 'a')
+ self.cache.set('b', 'b')
+ self.cache.set('c', 'c')
+ self.cache.set('d', 'd')
+ self.assertEqual(self.cache.get_many(['a', 'c', 'd']), {'a' : 'a', 'c' : 'c', 'd' : 'd'})
+ self.assertEqual(self.cache.get_many(['a', 'b', 'e']), {'a' : 'a', 'b' : 'b'})
+
+ def test_delete(self):
+ # Cache keys can be deleted
+ self.cache.set("key1", "spam")
+ self.cache.set("key2", "eggs")
+ self.assertEqual(self.cache.get("key1"), "spam")
+ self.cache.delete("key1")
+ self.assertEqual(self.cache.get("key1"), None)
+ self.assertEqual(self.cache.get("key2"), "eggs")
+
+ def test_has_key(self):
+ # The cache can be inspected for cache keys
+ self.cache.set("hello1", "goodbye1")
+ self.assertEqual(self.cache.has_key("hello1"), True)
+ self.assertEqual(self.cache.has_key("goodbye1"), False)
+
+ def test_in(self):
+ # The in operator can be used to inspet cache contents
+ self.cache.set("hello2", "goodbye2")
+ self.assertEqual("hello2" in self.cache, True)
+ self.assertEqual("goodbye2" in self.cache, False)
+
+ def test_incr(self):
+ # Cache values can be incremented
+ self.cache.set('answer', 41)
+ self.assertEqual(self.cache.incr('answer'), 42)
+ self.assertEqual(self.cache.get('answer'), 42)
+ self.assertEqual(self.cache.incr('answer', 10), 52)
+ self.assertEqual(self.cache.get('answer'), 52)
+ self.assertRaises(ValueError, self.cache.incr, 'does_not_exist')
+
+ def test_decr(self):
+ # Cache values can be decremented
+ self.cache.set('answer', 43)
+ self.assertEqual(self.cache.decr('answer'), 42)
+ self.assertEqual(self.cache.get('answer'), 42)
+ self.assertEqual(self.cache.decr('answer', 10), 32)
+ self.assertEqual(self.cache.get('answer'), 32)
+ self.assertRaises(ValueError, self.cache.decr, 'does_not_exist')
+
+ def test_data_types(self):
+ # Many different data types can be cached
+ stuff = {
+ 'string' : 'this is a string',
+ 'int' : 42,
+ 'list' : [1, 2, 3, 4],
+ 'tuple' : (1, 2, 3, 4),
+ 'dict' : {'A': 1, 'B' : 2},
+ 'function' : f,
+ 'class' : C,
+ }
+ self.cache.set("stuff", stuff)
+ self.assertEqual(self.cache.get("stuff"), stuff)
+
+ def test_cache_read_for_model_instance(self):
+ # Don't want fields with callable as default to be called on cache read
+ expensive_calculation.num_runs = 0
+ Poll.objects.all().delete()
+ my_poll = Poll.objects.create(question="Well?")
+ self.assertEqual(Poll.objects.count(), 1)
+ pub_date = my_poll.pub_date
+ self.cache.set('question', my_poll)
+ cached_poll = self.cache.get('question')
+ self.assertEqual(cached_poll.pub_date, pub_date)
+ # We only want the default expensive calculation run once
+ self.assertEqual(expensive_calculation.num_runs, 1)
+
+ def test_cache_write_for_model_instance_with_deferred(self):
+ # Don't want fields with callable as default to be called on cache write
+ expensive_calculation.num_runs = 0
+ Poll.objects.all().delete()
+ my_poll = Poll.objects.create(question="What?")
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ defer_qs = Poll.objects.all().defer('question')
+ self.assertEqual(defer_qs.count(), 1)
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ self.cache.set('deferred_queryset', defer_qs)
+ # cache set should not re-evaluate default functions
+ self.assertEqual(expensive_calculation.num_runs, 1)
+
+ def test_cache_read_for_model_instance_with_deferred(self):
+ # Don't want fields with callable as default to be called on cache read
+ expensive_calculation.num_runs = 0
+ Poll.objects.all().delete()
+ my_poll = Poll.objects.create(question="What?")
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ defer_qs = Poll.objects.all().defer('question')
+ self.assertEqual(defer_qs.count(), 1)
+ self.cache.set('deferred_queryset', defer_qs)
+ self.assertEqual(expensive_calculation.num_runs, 1)
+ runs_before_cache_read = expensive_calculation.num_runs
+ cached_polls = self.cache.get('deferred_queryset')
+ # We only want the default expensive calculation run on creation and set
+ self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read)
+
+ def test_expiration(self):
+ # Cache values can be set to expire
+ self.cache.set('expire1', 'very quickly', 1)
+ self.cache.set('expire2', 'very quickly', 1)
+ self.cache.set('expire3', 'very quickly', 1)
+
+ time.sleep(2)
+ self.assertEqual(self.cache.get("expire1"), None)
+
+ self.cache.add("expire2", "newvalue")
+ self.assertEqual(self.cache.get("expire2"), "newvalue")
+ self.assertEqual(self.cache.has_key("expire3"), False)
+
+ def test_unicode(self):
+ # Unicode values can be cached
+ stuff = {
+ u'ascii': u'ascii_value',
+ u'unicode_ascii': u'Iñtërnâtiônàlizætiøn1',
+ u'Iñtërnâtiônàlizætiøn': u'Iñtërnâtiônàlizætiøn2',
+ u'ascii': {u'x' : 1 }
+ }
+ for (key, value) in stuff.items():
+ self.cache.set(key, value)
+ self.assertEqual(self.cache.get(key), value)
+
+ def test_binary_string(self):
+ # Binary strings should be cachable
+ from zlib import compress, decompress
+ value = 'value_to_be_compressed'
+ compressed_value = compress(value)
+ self.cache.set('binary1', compressed_value)
+ compressed_result = self.cache.get('binary1')
+ self.assertEqual(compressed_value, compressed_result)
+ self.assertEqual(value, decompress(compressed_result))
+
+ def test_set_many(self):
+ # Multiple keys can be set using set_many
+ self.cache.set_many({"key1": "spam", "key2": "eggs"})
+ self.assertEqual(self.cache.get("key1"), "spam")
+ self.assertEqual(self.cache.get("key2"), "eggs")
+
+ def test_set_many_expiration(self):
+ # set_many takes a second ``timeout`` parameter
+ self.cache.set_many({"key1": "spam", "key2": "eggs"}, 1)
+ time.sleep(2)
+ self.assertEqual(self.cache.get("key1"), None)
+ self.assertEqual(self.cache.get("key2"), None)
+
+ def test_delete_many(self):
+ # Multiple keys can be deleted using delete_many
+ self.cache.set("key1", "spam")
+ self.cache.set("key2", "eggs")
+ self.cache.set("key3", "ham")
+ self.cache.delete_many(["key1", "key2"])
+ self.assertEqual(self.cache.get("key1"), None)
+ self.assertEqual(self.cache.get("key2"), None)
+ self.assertEqual(self.cache.get("key3"), "ham")
+
+ def test_clear(self):
+ # The cache can be emptied using clear
+ self.cache.set("key1", "spam")
+ self.cache.set("key2", "eggs")
+ self.cache.clear()
+ self.assertEqual(self.cache.get("key1"), None)
+ self.assertEqual(self.cache.get("key2"), None)
+
+ def test_long_timeout(self):
+ '''
+ Using a timeout greater than 30 days makes memcached think
+ it is an absolute expiration timestamp instead of a relative
+ offset. Test that we honour this convention. Refs #12399.
+ '''
+ self.cache.set('key1', 'eggs', 60*60*24*30 + 1) #30 days + 1 second
+ self.assertEqual(self.cache.get('key1'), 'eggs')
+
+ self.cache.add('key2', 'ham', 60*60*24*30 + 1)
+ self.assertEqual(self.cache.get('key2'), 'ham')
+
+ self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 60*60*24*30 + 1)
+ self.assertEqual(self.cache.get('key3'), 'sausage')
+ self.assertEqual(self.cache.get('key4'), 'lobster bisque')
+
+ def perform_cull_test(self, initial_count, final_count):
+ """This is implemented as a utility method, because only some of the backends
+ implement culling. The culling algorithm also varies slightly, so the final
+ number of entries will vary between backends"""
+ # Create initial cache key entries. This will overflow the cache, causing a cull
+ for i in range(1, initial_count):
+ self.cache.set('cull%d' % i, 'value', 1000)
+ count = 0
+ # Count how many keys are left in the cache.
+ for i in range(1, initial_count):
+ if self.cache.has_key('cull%d' % i):
+ count = count + 1
+ self.assertEqual(count, final_count)
+
+ def test_invalid_keys(self):
+ """
+ All the builtin backends (except memcached, see below) should warn on
+ keys that would be refused by memcached. This encourages portable
+ caching code without making it too difficult to use production backends
+ with more liberal key rules. Refs #6447.
+
+ """
+ # On Python 2.6+ we could use the catch_warnings context
+ # manager to test this warning nicely. Since we can't do that
+ # yet, the cleanest option is to temporarily ask for
+ # CacheKeyWarning to be raised as an exception.
+ _warnings_state = get_warnings_state()
+ warnings.simplefilter("error", CacheKeyWarning)
+
+ try:
+ # memcached does not allow whitespace or control characters in keys
+ self.assertRaises(CacheKeyWarning, self.cache.set, 'key with spaces', 'value')
+ # memcached limits key length to 250
+ self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value')
+ finally:
+ restore_warnings_state(_warnings_state)
+
+class DBCacheTests(unittest.TestCase, BaseCacheTests):
+ def setUp(self):
+ # Spaces are used in the table name to ensure quoting/escaping is working
+ self._table_name = 'test cache table'
+ management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
+ self.cache = get_cache('db://%s?max_entries=30' % self._table_name)
+
+ def tearDown(self):
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute('DROP TABLE %s' % connection.ops.quote_name(self._table_name))
+
+ def test_cull(self):
+ self.perform_cull_test(50, 29)
+
+class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
+ def setUp(self):
+ self.cache = get_cache('locmem://?max_entries=30')
+
+ def test_cull(self):
+ self.perform_cull_test(50, 29)
+
+# memcached backend isn't guaranteed to be available.
+# To check the memcached backend, the test settings file will
+# need to contain a CACHE_BACKEND setting that points at
+# your memcache server.
+if settings.CACHE_BACKEND.startswith('memcached://'):
+ class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
+ def setUp(self):
+ self.cache = get_cache(settings.CACHE_BACKEND)
+
+ def test_invalid_keys(self):
+ """
+ On memcached, we don't introduce a duplicate key validation
+ step (for speed reasons), we just let the memcached API
+ library raise its own exception on bad keys. Refs #6447.
+
+ In order to be memcached-API-library agnostic, we only assert
+ that a generic exception of some kind is raised.
+
+ """
+ # memcached does not allow whitespace or control characters in keys
+ self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value')
+ # memcached limits key length to 250
+ self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value')
+
+
+class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
+ """
+ Specific test cases for the file-based cache.
+ """
+ def setUp(self):
+ self.dirname = tempfile.mkdtemp()
+ self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
+
+ def test_hashing(self):
+ """Test that keys are hashed into subdirectories correctly"""
+ self.cache.set("foo", "bar")
+ keyhash = md5_constructor("foo").hexdigest()
+ keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
+ self.assert_(os.path.exists(keypath))
+
+ def test_subdirectory_removal(self):
+ """
+ Make sure that the created subdirectories are correctly removed when empty.
+ """
+ self.cache.set("foo", "bar")
+ keyhash = md5_constructor("foo").hexdigest()
+ keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
+ self.assert_(os.path.exists(keypath))
+
+ self.cache.delete("foo")
+ self.assert_(not os.path.exists(keypath))
+ self.assert_(not os.path.exists(os.path.dirname(keypath)))
+ self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
+
+ def test_cull(self):
+ self.perform_cull_test(50, 28)
+
+class CustomCacheKeyValidationTests(unittest.TestCase):
+ """
+ Tests for the ability to mixin a custom ``validate_key`` method to
+ a custom cache backend that otherwise inherits from a builtin
+ backend, and override the default key validation. Refs #6447.
+
+ """
+ def test_custom_key_validation(self):
+ cache = get_cache('regressiontests.cache.liberal_backend://')
+
+ # this key is both longer than 250 characters, and has spaces
+ key = 'some key with spaces' * 15
+ val = 'a value'
+ cache.set(key, val)
+ self.assertEqual(cache.get(key), val)
+
+class CacheUtils(unittest.TestCase):
+ """TestCase for django.utils.cache functions."""
+
+ def setUp(self):
+ self.path = '/cache/test/'
+ self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+ self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
+ self.orig_use_i18n = settings.USE_I18N
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
+ settings.CACHE_MIDDLEWARE_SECONDS = 1
+ settings.USE_I18N = False
+
+ def tearDown(self):
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
+ settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
+ settings.USE_I18N = self.orig_use_i18n
+
+ def _get_request(self, path):
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ }
+ request.path = request.path_info = "/cache/%s" % path
+ return request
+
+ def test_patch_vary_headers(self):
+ headers = (
+ # Initial vary, new headers, resulting vary.
+ (None, ('Accept-Encoding',), 'Accept-Encoding'),
+ ('Accept-Encoding', ('accept-encoding',), 'Accept-Encoding'),
+ ('Accept-Encoding', ('ACCEPT-ENCODING',), 'Accept-Encoding'),
+ ('Cookie', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ (None, ('Accept-Encoding', 'COOKIE'), 'Accept-Encoding, COOKIE'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ ('Cookie , Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ )
+ for initial_vary, newheaders, resulting_vary in headers:
+ response = HttpResponse()
+ if initial_vary is not None:
+ response['Vary'] = initial_vary
+ patch_vary_headers(response, newheaders)
+ self.assertEqual(response['Vary'], resulting_vary)
+
+ def test_get_cache_key(self):
+ request = self._get_request(self.path)
+ response = HttpResponse()
+ key_prefix = 'localprefix'
+ # Expect None if no headers have been set yet.
+ self.assertEqual(get_cache_key(request), None)
+ # Set headers to an empty list.
+ learn_cache_key(request, response)
+ self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+ # Verify that a specified key_prefix is taken in to account.
+ learn_cache_key(request, response, key_prefix=key_prefix)
+ self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+
+ def test_learn_cache_key(self):
+ request = self._get_request(self.path)
+ response = HttpResponse()
+ response['Vary'] = 'Pony'
+ # Make sure that the Vary header is added to the key hash
+ learn_cache_key(request, response)
+ self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+
+class CacheI18nTest(unittest.TestCase):
+
+ def setUp(self):
+ self.orig_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
+ self.orig_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+ self.orig_cache_backend = settings.CACHE_BACKEND
+ self.orig_use_i18n = settings.USE_I18N
+ self.orig_languages = settings.LANGUAGES
+ settings.LANGUAGES = (
+ ('en', 'English'),
+ ('es', 'Spanish'),
+ )
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
+ self.path = '/cache/test/'
+
+ def tearDown(self):
+ settings.CACHE_MIDDLEWARE_SECONDS = self.orig_cache_middleware_seconds
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.orig_cache_middleware_key_prefix
+ settings.CACHE_BACKEND = self.orig_cache_backend
+ settings.USE_I18N = self.orig_use_i18n
+ settings.LANGUAGES = self.orig_languages
+ translation.deactivate()
+
+ def _get_request(self):
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ }
+ request.path = request.path_info = self.path
+ return request
+
+ def _get_request_cache(self):
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ }
+ request.path = request.path_info = self.path
+ request._cache_update_cache = True
+ request.method = 'GET'
+ request.session = {}
+ return request
+
+ def test_cache_key_i18n(self):
+ settings.USE_I18N = True
+ request = self._get_request()
+ lang = translation.get_language()
+ response = HttpResponse()
+ key = learn_cache_key(request, response)
+ self.assertTrue(key.endswith(lang), "Cache keys should include the language name when i18n is active")
+ key2 = get_cache_key(request)
+ self.assertEqual(key, key2)
+
+ def test_cache_key_no_i18n (self):
+ settings.USE_I18N = False
+ request = self._get_request()
+ lang = translation.get_language()
+ response = HttpResponse()
+ key = learn_cache_key(request, response)
+ self.assertFalse(key.endswith(lang), "Cache keys shouldn't include the language name when i18n is inactive")
+
+ def test_middleware(self):
+ def set_cache(request, lang, msg):
+ translation.activate(lang)
+ response = HttpResponse()
+ response.content= msg
+ return UpdateCacheMiddleware().process_response(request, response)
+
+ settings.CACHE_MIDDLEWARE_SECONDS = 60
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX="test"
+ settings.CACHE_BACKEND='locmem:///'
+ settings.USE_I18N = True
+ en_message ="Hello world!"
+ es_message ="Hola mundo!"
+
+ request = self._get_request_cache()
+ set_cache(request, 'en', en_message)
+ get_cache_data = FetchFromCacheMiddleware().process_request(request)
+ # Check that we can recover the cache
+ self.assertNotEqual(get_cache_data.content, None)
+ self.assertEqual(en_message, get_cache_data.content)
+ # change the session language and set content
+ request = self._get_request_cache()
+ set_cache(request, 'es', es_message)
+ # change again the language
+ translation.activate('en')
+ # retrieve the content from cache
+ get_cache_data = FetchFromCacheMiddleware().process_request(request)
+ self.assertEqual(get_cache_data.content, en_message)
+ # change again the language
+ translation.activate('es')
+ get_cache_data = FetchFromCacheMiddleware().process_request(request)
+ self.assertEqual(get_cache_data.content, es_message)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/parts/django/tests/regressiontests/comment_tests/__init__.py b/parts/django/tests/regressiontests/comment_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/__init__.py
diff --git a/parts/django/tests/regressiontests/comment_tests/custom_comments/__init__.py b/parts/django/tests/regressiontests/comment_tests/custom_comments/__init__.py
new file mode 100644
index 0000000..598927e
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/custom_comments/__init__.py
@@ -0,0 +1,32 @@
+from django.core import urlresolvers
+from regressiontests.comment_tests.custom_comments.models import CustomComment
+from regressiontests.comment_tests.custom_comments.forms import CustomCommentForm
+
+def get_model():
+ return CustomComment
+
+def get_form():
+ return CustomCommentForm
+
+def get_form_target():
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_submit_comment"
+ )
+
+def get_flag_url(c):
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_flag_comment",
+ args=(c.id,)
+ )
+
+def get_delete_url(c):
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_delete_comment",
+ args=(c.id,)
+ )
+
+def get_approve_url(c):
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_approve_comment",
+ args=(c.id,)
+ )
diff --git a/parts/django/tests/regressiontests/comment_tests/custom_comments/forms.py b/parts/django/tests/regressiontests/comment_tests/custom_comments/forms.py
new file mode 100644
index 0000000..b788cdc
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/custom_comments/forms.py
@@ -0,0 +1,4 @@
+from django import forms
+
+class CustomCommentForm(forms.Form):
+ pass
diff --git a/parts/django/tests/regressiontests/comment_tests/custom_comments/models.py b/parts/django/tests/regressiontests/comment_tests/custom_comments/models.py
new file mode 100644
index 0000000..592ad79
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/custom_comments/models.py
@@ -0,0 +1,4 @@
+from django.db import models
+
+class CustomComment(models.Model):
+ pass
diff --git a/parts/django/tests/regressiontests/comment_tests/custom_comments/views.py b/parts/django/tests/regressiontests/comment_tests/custom_comments/views.py
new file mode 100644
index 0000000..93cea9d
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/custom_comments/views.py
@@ -0,0 +1,13 @@
+from django.http import HttpResponse
+
+def custom_submit_comment(request):
+ return HttpResponse("Hello from the custom submit comment view.")
+
+def custom_flag_comment(request, comment_id):
+ return HttpResponse("Hello from the custom flag view.")
+
+def custom_delete_comment(request, comment_id):
+ return HttpResponse("Hello from the custom delete view.")
+
+def custom_approve_comment(request, comment_id):
+ return HttpResponse("Hello from the custom approve view.")
diff --git a/parts/django/tests/regressiontests/comment_tests/fixtures/comment_tests.json b/parts/django/tests/regressiontests/comment_tests/fixtures/comment_tests.json
new file mode 100644
index 0000000..55e2161
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/fixtures/comment_tests.json
@@ -0,0 +1,53 @@
+[
+ {
+ "model" : "comment_tests.book",
+ "pk" : 1,
+ "fields" : {
+ "dewey_decimal" : "12.34"
+ }
+ },
+ {
+ "model" : "comment_tests.author",
+ "pk" : 1,
+ "fields" : {
+ "first_name" : "John",
+ "last_name" : "Smith"
+ }
+ },
+ {
+ "model" : "comment_tests.author",
+ "pk" : 2,
+ "fields" : {
+ "first_name" : "Peter",
+ "last_name" : "Jones"
+ }
+ },
+ {
+ "model" : "comment_tests.article",
+ "pk" : 1,
+ "fields" : {
+ "author" : 1,
+ "headline" : "Man Bites Dog"
+ }
+ },
+ {
+ "model" : "comment_tests.article",
+ "pk" : 2,
+ "fields" : {
+ "author" : 2,
+ "headline" : "Dog Bites Man"
+ }
+ },
+
+ {
+ "model" : "auth.user",
+ "pk" : 100,
+ "fields" : {
+ "username" : "normaluser",
+ "password" : "34ea4aaaf24efcbb4b30d27302f8657f",
+ "first_name": "Joe",
+ "last_name": "Normal",
+ "email": "joe.normal@example.com"
+ }
+ }
+]
diff --git a/parts/django/tests/regressiontests/comment_tests/fixtures/comment_utils.xml b/parts/django/tests/regressiontests/comment_tests/fixtures/comment_utils.xml
new file mode 100644
index 0000000..a39bbf6
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/fixtures/comment_utils.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="comment_tests.entry">
+ <field type="CharField" name="title">ABC</field>
+ <field type="TextField" name="body">This is the body</field>
+ <field type="DateField" name="pub_date">2008-01-01</field>
+ <field type="BooleanField" name="enable_comments">True</field>
+ </object>
+ <object pk="2" model="comment_tests.entry">
+ <field type="CharField" name="title">XYZ</field>
+ <field type="TextField" name="body">Text here</field>
+ <field type="DateField" name="pub_date">2008-01-02</field>
+ <field type="BooleanField" name="enable_comments">False</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/comment_tests/models.py b/parts/django/tests/regressiontests/comment_tests/models.py
new file mode 100644
index 0000000..8877ea1
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/models.py
@@ -0,0 +1,34 @@
+"""
+Comments may be attached to any object. See the comment documentation for
+more information.
+"""
+
+from django.db import models
+from django.test import TestCase
+
+class Author(models.Model):
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=30)
+
+ def __str__(self):
+ return '%s %s' % (self.first_name, self.last_name)
+
+class Article(models.Model):
+ author = models.ForeignKey(Author)
+ headline = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.headline
+
+class Entry(models.Model):
+ title = models.CharField(max_length=250)
+ body = models.TextField()
+ pub_date = models.DateField()
+ enable_comments = models.BooleanField()
+
+ def __str__(self):
+ return self.title
+
+class Book(models.Model):
+ dewey_decimal = models.DecimalField(primary_key = True, decimal_places=2, max_digits=5)
+ \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/__init__.py b/parts/django/tests/regressiontests/comment_tests/tests/__init__.py
new file mode 100644
index 0000000..449fea4
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/__init__.py
@@ -0,0 +1,89 @@
+from django.contrib.auth.models import User
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sites.models import Site
+from django.test import TestCase
+from regressiontests.comment_tests.models import Article, Author
+
+# Shortcut
+CT = ContentType.objects.get_for_model
+
+# Helper base class for comment tests that need data.
+class CommentTestCase(TestCase):
+ fixtures = ["comment_tests"]
+ urls = 'django.contrib.comments.urls'
+
+ def createSomeComments(self):
+ # Two anonymous comments on two different objects
+ c1 = Comment.objects.create(
+ content_type = CT(Article),
+ object_pk = "1",
+ user_name = "Joe Somebody",
+ user_email = "jsomebody@example.com",
+ user_url = "http://example.com/~joe/",
+ comment = "First!",
+ site = Site.objects.get_current(),
+ )
+ c2 = Comment.objects.create(
+ content_type = CT(Author),
+ object_pk = "1",
+ user_name = "Joe Somebody",
+ user_email = "jsomebody@example.com",
+ user_url = "http://example.com/~joe/",
+ comment = "First here, too!",
+ site = Site.objects.get_current(),
+ )
+
+ # Two authenticated comments: one on the same Article, and
+ # one on a different Author
+ user = User.objects.create(
+ username = "frank_nobody",
+ first_name = "Frank",
+ last_name = "Nobody",
+ email = "fnobody@example.com",
+ password = "",
+ is_staff = False,
+ is_active = True,
+ is_superuser = False,
+ )
+ c3 = Comment.objects.create(
+ content_type = CT(Article),
+ object_pk = "1",
+ user = user,
+ user_url = "http://example.com/~frank/",
+ comment = "Damn, I wanted to be first.",
+ site = Site.objects.get_current(),
+ )
+ c4 = Comment.objects.create(
+ content_type = CT(Author),
+ object_pk = "2",
+ user = user,
+ user_url = "http://example.com/~frank/",
+ comment = "You get here first, too?",
+ site = Site.objects.get_current(),
+ )
+
+ return c1, c2, c3, c4
+
+ def getData(self):
+ return {
+ 'name' : 'Jim Bob',
+ 'email' : 'jim.bob@example.com',
+ 'url' : '',
+ 'comment' : 'This is my comment',
+ }
+
+ def getValidData(self, obj):
+ f = CommentForm(obj)
+ d = self.getData()
+ d.update(f.initial)
+ return d
+
+from regressiontests.comment_tests.tests.app_api_tests import *
+from regressiontests.comment_tests.tests.model_tests import *
+from regressiontests.comment_tests.tests.comment_form_tests import *
+from regressiontests.comment_tests.tests.templatetag_tests import *
+from regressiontests.comment_tests.tests.comment_view_tests import *
+from regressiontests.comment_tests.tests.moderation_view_tests import *
+from regressiontests.comment_tests.tests.comment_utils_moderators_tests import *
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/app_api_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/app_api_tests.py
new file mode 100644
index 0000000..c4d9ebf
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/app_api_tests.py
@@ -0,0 +1,71 @@
+from django.conf import settings
+from django.contrib import comments
+from django.contrib.comments.models import Comment
+from django.contrib.comments.forms import CommentForm
+from regressiontests.comment_tests.tests import CommentTestCase
+
+class CommentAppAPITests(CommentTestCase):
+ """Tests for the "comment app" API"""
+
+ def testGetCommentApp(self):
+ self.assertEqual(comments.get_comment_app(), comments)
+
+ def testGetForm(self):
+ self.assertEqual(comments.get_form(), CommentForm)
+
+ def testGetFormTarget(self):
+ self.assertEqual(comments.get_form_target(), "/post/")
+
+ def testGetFlagURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_flag_url(c), "/flag/12345/")
+
+ def getGetDeleteURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_delete_url(c), "/delete/12345/")
+
+ def getGetApproveURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_approve_url(c), "/approve/12345/")
+
+
+class CustomCommentTest(CommentTestCase):
+ urls = 'regressiontests.comment_tests.urls'
+
+ def setUp(self):
+ self.old_comments_app = getattr(settings, 'COMMENTS_APP', None)
+ settings.COMMENTS_APP = 'regressiontests.comment_tests.custom_comments'
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + [settings.COMMENTS_APP,]
+
+ def tearDown(self):
+ del settings.INSTALLED_APPS[-1]
+ settings.COMMENTS_APP = self.old_comments_app
+ if settings.COMMENTS_APP is None:
+ delattr(settings._wrapped, 'COMMENTS_APP')
+
+ def testGetCommentApp(self):
+ from regressiontests.comment_tests import custom_comments
+ self.assertEqual(comments.get_comment_app(), custom_comments)
+
+ def testGetModel(self):
+ from regressiontests.comment_tests.custom_comments.models import CustomComment
+ self.assertEqual(comments.get_model(), CustomComment)
+
+ def testGetForm(self):
+ from regressiontests.comment_tests.custom_comments.forms import CustomCommentForm
+ self.assertEqual(comments.get_form(), CustomCommentForm)
+
+ def testGetFormTarget(self):
+ self.assertEqual(comments.get_form_target(), "/post/")
+
+ def testGetFlagURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_flag_url(c), "/flag/12345/")
+
+ def getGetDeleteURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_delete_url(c), "/delete/12345/")
+
+ def getGetApproveURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_approve_url(c), "/approve/12345/")
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/comment_form_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/comment_form_tests.py
new file mode 100644
index 0000000..8dfcc07
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/comment_form_tests.py
@@ -0,0 +1,84 @@
+import time
+
+from django.conf import settings
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+from django.utils.hashcompat import sha_constructor
+
+from regressiontests.comment_tests.models import Article
+from regressiontests.comment_tests.tests import CommentTestCase
+
+
+class CommentFormTests(CommentTestCase):
+ def testInit(self):
+ f = CommentForm(Article.objects.get(pk=1))
+ self.assertEqual(f.initial['content_type'], str(Article._meta))
+ self.assertEqual(f.initial['object_pk'], "1")
+ self.assertNotEqual(f.initial['security_hash'], None)
+ self.assertNotEqual(f.initial['timestamp'], None)
+
+ def testValidPost(self):
+ a = Article.objects.get(pk=1)
+ f = CommentForm(a, data=self.getValidData(a))
+ self.assert_(f.is_valid(), f.errors)
+ return f
+
+ def tamperWithForm(self, **kwargs):
+ a = Article.objects.get(pk=1)
+ d = self.getValidData(a)
+ d.update(kwargs)
+ f = CommentForm(Article.objects.get(pk=1), data=d)
+ self.assertFalse(f.is_valid())
+ return f
+
+ def testHoneypotTampering(self):
+ self.tamperWithForm(honeypot="I am a robot")
+
+ def testTimestampTampering(self):
+ self.tamperWithForm(timestamp=str(time.time() - 28800))
+
+ def testSecurityHashTampering(self):
+ self.tamperWithForm(security_hash="Nobody expects the Spanish Inquisition!")
+
+ def testContentTypeTampering(self):
+ self.tamperWithForm(content_type="auth.user")
+
+ def testObjectPKTampering(self):
+ self.tamperWithForm(object_pk="3")
+
+ def testSecurityErrors(self):
+ f = self.tamperWithForm(honeypot="I am a robot")
+ self.assert_("honeypot" in f.security_errors())
+
+ def testGetCommentObject(self):
+ f = self.testValidPost()
+ c = f.get_comment_object()
+ self.assert_(isinstance(c, Comment))
+ self.assertEqual(c.content_object, Article.objects.get(pk=1))
+ self.assertEqual(c.comment, "This is my comment")
+ c.save()
+ self.assertEqual(Comment.objects.count(), 1)
+
+ def testProfanities(self):
+ """Test COMMENTS_ALLOW_PROFANITIES and PROFANITIES_LIST settings"""
+ a = Article.objects.get(pk=1)
+ d = self.getValidData(a)
+
+ # Save settings in case other tests need 'em
+ saved = settings.PROFANITIES_LIST, settings.COMMENTS_ALLOW_PROFANITIES
+
+ # Don't wanna swear in the unit tests if we don't have to...
+ settings.PROFANITIES_LIST = ["rooster"]
+
+ # Try with COMMENTS_ALLOW_PROFANITIES off
+ settings.COMMENTS_ALLOW_PROFANITIES = False
+ f = CommentForm(a, data=dict(d, comment="What a rooster!"))
+ self.assertFalse(f.is_valid())
+
+ # Now with COMMENTS_ALLOW_PROFANITIES on
+ settings.COMMENTS_ALLOW_PROFANITIES = True
+ f = CommentForm(a, data=dict(d, comment="What a rooster!"))
+ self.assertTrue(f.is_valid())
+
+ # Restore settings
+ settings.PROFANITIES_LIST, settings.COMMENTS_ALLOW_PROFANITIES = saved
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py
new file mode 100644
index 0000000..2e93b8b
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py
@@ -0,0 +1,75 @@
+from regressiontests.comment_tests.tests import CommentTestCase, CT, Site
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+from django.contrib.comments.moderation import moderator, CommentModerator, AlreadyModerated
+from regressiontests.comment_tests.models import Entry
+from django.core import mail
+
+class EntryModerator1(CommentModerator):
+ email_notification = True
+
+class EntryModerator2(CommentModerator):
+ enable_field = 'enable_comments'
+
+class EntryModerator3(CommentModerator):
+ auto_close_field = 'pub_date'
+ close_after = 7
+
+class EntryModerator4(CommentModerator):
+ auto_moderate_field = 'pub_date'
+ moderate_after = 7
+
+class CommentUtilsModeratorTests(CommentTestCase):
+ fixtures = ["comment_utils.xml"]
+
+ def createSomeComments(self):
+ # Tests for the moderation signals must actually post data
+ # through the comment views, because only the comment views
+ # emit the custom signals moderation listens for.
+ e = Entry.objects.get(pk=1)
+ data = self.getValidData(e)
+
+ self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+
+ # We explicitly do a try/except to get the comment we've just
+ # posted because moderation may have disallowed it, in which
+ # case we can just return it as None.
+ try:
+ c1 = Comment.objects.all()[0]
+ except IndexError:
+ c1 = None
+
+ self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+
+ try:
+ c2 = Comment.objects.all()[0]
+ except IndexError:
+ c2 = None
+ return c1, c2
+
+ def tearDown(self):
+ moderator.unregister(Entry)
+
+ def testRegisterExistingModel(self):
+ moderator.register(Entry, EntryModerator1)
+ self.assertRaises(AlreadyModerated, moderator.register, Entry, EntryModerator1)
+
+ def testEmailNotification(self):
+ moderator.register(Entry, EntryModerator1)
+ self.createSomeComments()
+ self.assertEquals(len(mail.outbox), 2)
+
+ def testCommentsEnabled(self):
+ moderator.register(Entry, EntryModerator2)
+ self.createSomeComments()
+ self.assertEquals(Comment.objects.all().count(), 1)
+
+ def testAutoCloseField(self):
+ moderator.register(Entry, EntryModerator3)
+ self.createSomeComments()
+ self.assertEquals(Comment.objects.all().count(), 0)
+
+ def testAutoModerateField(self):
+ moderator.register(Entry, EntryModerator4)
+ c1, c2 = self.createSomeComments()
+ self.assertEquals(c2.is_public, False)
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/comment_view_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/comment_view_tests.py
new file mode 100644
index 0000000..b8a62b4
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/comment_view_tests.py
@@ -0,0 +1,258 @@
+import re
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.comments import signals
+from django.contrib.comments.models import Comment
+from regressiontests.comment_tests.models import Article, Book
+from regressiontests.comment_tests.tests import CommentTestCase
+
+post_redirect_re = re.compile(r'^http://testserver/posted/\?c=(?P<pk>\d+$)')
+
+class CommentViewTests(CommentTestCase):
+
+ def testPostCommentHTTPMethods(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.get("/post/", data)
+ self.assertEqual(response.status_code, 405)
+ self.assertEqual(response["Allow"], "POST")
+
+ def testPostCommentMissingCtype(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ del data["content_type"]
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostCommentBadCtype(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["content_type"] = "Nobody expects the Spanish Inquisition!"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostCommentMissingObjectPK(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ del data["object_pk"]
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostCommentBadObjectPK(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["object_pk"] = "14"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostInvalidIntegerPK(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["comment"] = "This is another comment"
+ data["object_pk"] = u'\ufffd'
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostInvalidDecimalPK(self):
+ b = Book.objects.get(pk='12.34')
+ data = self.getValidData(b)
+ data["comment"] = "This is another comment"
+ data["object_pk"] = 'cookies'
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testCommentPreview(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["preview"] = "Preview"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "comments/preview.html")
+
+ def testHashTampering(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["security_hash"] = "Nobody expects the Spanish Inquisition!"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testDebugCommentErrors(self):
+ """The debug error template should be shown only if DEBUG is True"""
+ olddebug = settings.DEBUG
+
+ settings.DEBUG = True
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["security_hash"] = "Nobody expects the Spanish Inquisition!"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+ self.assertTemplateUsed(response, "comments/400-debug.html")
+
+ settings.DEBUG = False
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+ self.assertTemplateNotUsed(response, "comments/400-debug.html")
+
+ settings.DEBUG = olddebug
+
+ def testCreateValidComment(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ self.response = self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+ self.assertEqual(self.response.status_code, 302)
+ self.assertEqual(Comment.objects.count(), 1)
+ c = Comment.objects.all()[0]
+ self.assertEqual(c.ip_address, "1.2.3.4")
+ self.assertEqual(c.comment, "This is my comment")
+
+ def testPostAsAuthenticatedUser(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data['name'] = data['email'] = ''
+ self.client.login(username="normaluser", password="normaluser")
+ self.response = self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+ self.assertEqual(self.response.status_code, 302)
+ self.assertEqual(Comment.objects.count(), 1)
+ c = Comment.objects.all()[0]
+ self.assertEqual(c.ip_address, "1.2.3.4")
+ u = User.objects.get(username='normaluser')
+ self.assertEqual(c.user, u)
+ self.assertEqual(c.user_name, u.get_full_name())
+ self.assertEqual(c.user_email, u.email)
+
+ def testPostAsAuthenticatedUserWithoutFullname(self):
+ """
+ Check that the user's name in the comment is populated for
+ authenticated users without first_name and last_name.
+ """
+ user = User.objects.create_user(username='jane_other',
+ email='jane@example.com', password='jane_other')
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data['name'] = data['email'] = ''
+ self.client.login(username="jane_other", password="jane_other")
+ self.response = self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+ c = Comment.objects.get(user=user)
+ self.assertEqual(c.ip_address, "1.2.3.4")
+ self.assertEqual(c.user_name, 'jane_other')
+ user.delete()
+
+ def testPreventDuplicateComments(self):
+ """Prevent posting the exact same comment twice"""
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ self.client.post("/post/", data)
+ self.client.post("/post/", data)
+ self.assertEqual(Comment.objects.count(), 1)
+
+ # This should not trigger the duplicate prevention
+ self.client.post("/post/", dict(data, comment="My second comment."))
+ self.assertEqual(Comment.objects.count(), 2)
+
+ def testCommentSignals(self):
+ """Test signals emitted by the comment posting view"""
+
+ # callback
+ def receive(sender, **kwargs):
+ self.assertEqual(kwargs['comment'].comment, "This is my comment")
+ self.assert_('request' in kwargs)
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ expected_signals = [
+ signals.comment_will_be_posted, signals.comment_was_posted
+ ]
+ for signal in expected_signals:
+ signal.connect(receive)
+
+ # Post a comment and check the signals
+ self.testCreateValidComment()
+ self.assertEqual(received_signals, expected_signals)
+
+ for signal in expected_signals:
+ signal.disconnect(receive)
+
+ def testWillBePostedSignal(self):
+ """
+ Test that the comment_will_be_posted signal can prevent the comment from
+ actually getting saved
+ """
+ def receive(sender, **kwargs): return False
+ signals.comment_will_be_posted.connect(receive, dispatch_uid="comment-test")
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(Comment.objects.count(), 0)
+ signals.comment_will_be_posted.disconnect(dispatch_uid="comment-test")
+
+ def testWillBePostedSignalModifyComment(self):
+ """
+ Test that the comment_will_be_posted signal can modify a comment before
+ it gets posted
+ """
+ def receive(sender, **kwargs):
+ # a bad but effective spam filter :)...
+ kwargs['comment'].is_public = False
+
+ signals.comment_will_be_posted.connect(receive)
+ self.testCreateValidComment()
+ c = Comment.objects.all()[0]
+ self.assertFalse(c.is_public)
+
+ def testCommentNext(self):
+ """Test the different "next" actions the comment view can take"""
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = post_redirect_re.match(location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ data["next"] = "/somewhere/else/"
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ def testCommentDoneView(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = post_redirect_re.match(location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+ pk = int(match.group('pk'))
+ response = self.client.get(location)
+ self.assertTemplateUsed(response, "comments/posted.html")
+ self.assertEqual(response.context[0]["comment"], Comment.objects.get(pk=pk))
+
+ def testCommentNextWithQueryString(self):
+ """
+ The `next` key needs to handle already having a query string (#10585)
+ """
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["next"] = "/somewhere/else/?foo=bar"
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = re.search(r"^http://testserver/somewhere/else/\?foo=bar&c=\d+$", location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ def testCommentPostRedirectWithInvalidIntegerPK(self):
+ """
+ Tests that attempting to retrieve the location specified in the
+ post redirect, after adding some invalid data to the expected
+ querystring it ends with, doesn't cause a server error.
+ """
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ broken_location = location + u"\ufffd"
+ response = self.client.get(broken_location)
+ self.assertEqual(response.status_code, 200)
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/model_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/model_tests.py
new file mode 100644
index 0000000..2cbf66f
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/model_tests.py
@@ -0,0 +1,49 @@
+from django.contrib.comments.models import Comment
+
+from regressiontests.comment_tests.models import Author, Article
+from regressiontests.comment_tests.tests import CommentTestCase
+
+
+class CommentModelTests(CommentTestCase):
+ def testSave(self):
+ for c in self.createSomeComments():
+ self.assertNotEqual(c.submit_date, None)
+
+ def testUserProperties(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ self.assertEqual(c1.name, "Joe Somebody")
+ self.assertEqual(c2.email, "jsomebody@example.com")
+ self.assertEqual(c3.name, "Frank Nobody")
+ self.assertEqual(c3.url, "http://example.com/~frank/")
+ self.assertEqual(c1.user, None)
+ self.assertEqual(c3.user, c4.user)
+
+class CommentManagerTests(CommentTestCase):
+
+ def testInModeration(self):
+ """Comments that aren't public are considered in moderation"""
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False
+ c2.is_public = False
+ c1.save()
+ c2.save()
+ moderated_comments = list(Comment.objects.in_moderation().order_by("id"))
+ self.assertEqual(moderated_comments, [c1, c2])
+
+ def testRemovedCommentsNotInModeration(self):
+ """Removed comments are not considered in moderation"""
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False
+ c2.is_public = False
+ c2.is_removed = True
+ c1.save()
+ c2.save()
+ moderated_comments = list(Comment.objects.in_moderation())
+ self.assertEqual(moderated_comments, [c1])
+
+ def testForModel(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ article_comments = list(Comment.objects.for_model(Article).order_by("id"))
+ author_comments = list(Comment.objects.for_model(Author.objects.get(pk=1)))
+ self.assertEqual(article_comments, [c1, c3])
+ self.assertEqual(author_comments, [c2])
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/moderation_view_tests.py
new file mode 100644
index 0000000..e5094ab
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/moderation_view_tests.py
@@ -0,0 +1,203 @@
+from django.contrib.auth.models import User, Permission
+from django.contrib.comments import signals
+from django.contrib.comments.models import Comment, CommentFlag
+from django.contrib.contenttypes.models import ContentType
+
+from regressiontests.comment_tests.tests import CommentTestCase
+
+
+class FlagViewTests(CommentTestCase):
+
+ def testFlagGet(self):
+ """GET the flag view: render a confirmation page."""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/flag/%d/" % pk)
+ self.assertTemplateUsed(response, "comments/flag.html")
+
+ def testFlagPost(self):
+ """POST the flag view: actually flag the view (nice for XHR)"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/flag/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/flagged/?c=%d" % pk)
+ c = Comment.objects.get(pk=pk)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
+ return c
+
+ def testFlagPostTwice(self):
+ """Users don't get to flag comments more than once."""
+ c = self.testFlagPost()
+ self.client.post("/flag/%d/" % c.pk)
+ self.client.post("/flag/%d/" % c.pk)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
+
+ def testFlagAnon(self):
+ """GET/POST the flag view while not logged in: redirect to log in."""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/flag/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
+ response = self.client.post("/flag/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
+
+ def testFlaggedView(self):
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/flagged/", data={"c":pk})
+ self.assertTemplateUsed(response, "comments/flagged.html")
+
+ def testFlagSignals(self):
+ """Test signals emitted by the comment flag view"""
+
+ # callback
+ def receive(sender, **kwargs):
+ self.assertEqual(kwargs['flag'].flag, CommentFlag.SUGGEST_REMOVAL)
+ self.assertEqual(kwargs['request'].user.username, "normaluser")
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ signals.comment_was_flagged.connect(receive)
+
+ # Post a comment and check the signals
+ self.testFlagPost()
+ self.assertEqual(received_signals, [signals.comment_was_flagged])
+
+def makeModerator(username):
+ u = User.objects.get(username=username)
+ ct = ContentType.objects.get_for_model(Comment)
+ p = Permission.objects.get(content_type=ct, codename="can_moderate")
+ u.user_permissions.add(p)
+
+class DeleteViewTests(CommentTestCase):
+
+ def testDeletePermissions(self):
+ """The delete view should only be accessible to 'moderators'"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/delete/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/delete/%d/" % pk)
+
+ makeModerator("normaluser")
+ response = self.client.get("/delete/%d/" % pk)
+ self.assertEqual(response.status_code, 200)
+
+ def testDeletePost(self):
+ """POSTing the delete view should mark the comment as removed"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/delete/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/deleted/?c=%d" % pk)
+ c = Comment.objects.get(pk=pk)
+ self.assertTrue(c.is_removed)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1)
+
+ def testDeleteSignals(self):
+ def receive(sender, **kwargs):
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ signals.comment_was_flagged.connect(receive)
+
+ # Post a comment and check the signals
+ self.testDeletePost()
+ self.assertEqual(received_signals, [signals.comment_was_flagged])
+
+ def testDeletedView(self):
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/deleted/", data={"c":pk})
+ self.assertTemplateUsed(response, "comments/deleted.html")
+
+class ApproveViewTests(CommentTestCase):
+
+ def testApprovePermissions(self):
+ """The delete view should only be accessible to 'moderators'"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/approve/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/approve/%d/" % pk)
+
+ makeModerator("normaluser")
+ response = self.client.get("/approve/%d/" % pk)
+ self.assertEqual(response.status_code, 200)
+
+ def testApprovePost(self):
+ """POSTing the delete view should mark the comment as removed"""
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False; c1.save()
+
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/approve/%d/" % c1.pk)
+ self.assertEqual(response["Location"], "http://testserver/approved/?c=%d" % c1.pk)
+ c = Comment.objects.get(pk=c1.pk)
+ self.assertTrue(c.is_public)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1)
+
+ def testApproveSignals(self):
+ def receive(sender, **kwargs):
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ signals.comment_was_flagged.connect(receive)
+
+ # Post a comment and check the signals
+ self.testApprovePost()
+ self.assertEqual(received_signals, [signals.comment_was_flagged])
+
+ def testApprovedView(self):
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/approved/", data={"c":pk})
+ self.assertTemplateUsed(response, "comments/approved.html")
+
+class AdminActionsTests(CommentTestCase):
+ urls = "regressiontests.comment_tests.urls_admin"
+
+ def setUp(self):
+ super(AdminActionsTests, self).setUp()
+
+ # Make "normaluser" a moderator
+ u = User.objects.get(username="normaluser")
+ u.is_staff = True
+ perms = Permission.objects.filter(
+ content_type__app_label = 'comments',
+ codename__endswith = 'comment'
+ )
+ for perm in perms:
+ u.user_permissions.add(perm)
+ u.save()
+
+ def testActionsNonModerator(self):
+ comments = self.createSomeComments()
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/admin/comments/comment/")
+ self.assertEquals("approve_comments" in response.content, False)
+
+ def testActionsModerator(self):
+ comments = self.createSomeComments()
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/admin/comments/comment/")
+ self.assertEquals("approve_comments" in response.content, True)
+
+ def testActionsDisabledDelete(self):
+ "Tests a CommentAdmin where 'delete_selected' has been disabled."
+ comments = self.createSomeComments()
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get('/admin2/comments/comment/')
+ self.assertEqual(response.status_code, 200)
+ self.assert_(
+ '<option value="delete_selected">' not in response.content,
+ "Found an unexpected delete_selected in response"
+ )
diff --git a/parts/django/tests/regressiontests/comment_tests/tests/templatetag_tests.py b/parts/django/tests/regressiontests/comment_tests/tests/templatetag_tests.py
new file mode 100644
index 0000000..29a097a
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/tests/templatetag_tests.py
@@ -0,0 +1,97 @@
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+from django.contrib.contenttypes.models import ContentType
+from django.template import Template, Context
+from regressiontests.comment_tests.models import Article, Author
+from regressiontests.comment_tests.tests import CommentTestCase
+
+class CommentTemplateTagTests(CommentTestCase):
+
+ def render(self, t, **c):
+ ctx = Context(c)
+ out = Template(t).render(ctx)
+ return ctx, out
+
+ def testCommentFormTarget(self):
+ ctx, out = self.render("{% load comments %}{% comment_form_target %}")
+ self.assertEqual(out, "/post/")
+
+ def testGetCommentForm(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% get_comment_form for comment_tests.article a.id as form %}")
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assertEqual(out, "")
+ self.assert_(isinstance(ctx["form"], CommentForm))
+
+ def testGetCommentFormFromLiteral(self):
+ self.testGetCommentForm("{% get_comment_form for comment_tests.article 1 as form %}")
+
+ def testGetCommentFormFromObject(self):
+ self.testGetCommentForm("{% get_comment_form for a as form %}")
+
+ def testRenderCommentForm(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% render_comment_form for comment_tests.article a.id %}")
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assert_(out.strip().startswith("<form action="))
+ self.assert_(out.strip().endswith("</form>"))
+
+ def testRenderCommentFormFromLiteral(self):
+ self.testRenderCommentForm("{% render_comment_form for comment_tests.article 1 %}")
+
+ def testRenderCommentFormFromObject(self):
+ self.testRenderCommentForm("{% render_comment_form for a %}")
+
+ def testGetCommentCount(self, tag=None):
+ self.createSomeComments()
+ t = "{% load comments %}" + (tag or "{% get_comment_count for comment_tests.article a.id as cc %}") + "{{ cc }}"
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assertEqual(out, "2")
+
+ def testGetCommentCountFromLiteral(self):
+ self.testGetCommentCount("{% get_comment_count for comment_tests.article 1 as cc %}")
+
+ def testGetCommentCountFromObject(self):
+ self.testGetCommentCount("{% get_comment_count for a as cc %}")
+
+ def testGetCommentList(self, tag=None):
+ c1, c2, c3, c4 = self.createSomeComments()
+ t = "{% load comments %}" + (tag or "{% get_comment_list for comment_tests.author a.id as cl %}")
+ ctx, out = self.render(t, a=Author.objects.get(pk=1))
+ self.assertEqual(out, "")
+ self.assertEqual(list(ctx["cl"]), [c2])
+
+ def testGetCommentListFromLiteral(self):
+ self.testGetCommentList("{% get_comment_list for comment_tests.author 1 as cl %}")
+
+ def testGetCommentListFromObject(self):
+ self.testGetCommentList("{% get_comment_list for a as cl %}")
+
+ def testGetCommentPermalink(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ t = "{% load comments %}{% get_comment_list for comment_tests.author author.id as cl %}"
+ t += "{% get_comment_permalink cl.0 %}"
+ ct = ContentType.objects.get_for_model(Author)
+ author = Author.objects.get(pk=1)
+ ctx, out = self.render(t, author=author)
+ self.assertEqual(out, "/cr/%s/%s/#c%s" % (ct.id, author.id, c2.id))
+
+ def testGetCommentPermalinkFormatted(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ t = "{% load comments %}{% get_comment_list for comment_tests.author author.id as cl %}"
+ t += "{% get_comment_permalink cl.0 '#c%(id)s-by-%(user_name)s' %}"
+ ct = ContentType.objects.get_for_model(Author)
+ author = Author.objects.get(pk=1)
+ ctx, out = self.render(t, author=author)
+ self.assertEqual(out, "/cr/%s/%s/#c%s-by-Joe Somebody" % (ct.id, author.id, c2.id))
+
+ def testRenderCommentList(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% render_comment_list for comment_tests.article a.id %}")
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assert_(out.strip().startswith("<dl id=\"comments\">"))
+ self.assert_(out.strip().endswith("</dl>"))
+
+ def testRenderCommentListFromLiteral(self):
+ self.testRenderCommentList("{% render_comment_list for comment_tests.article 1 %}")
+
+ def testRenderCommentListFromObject(self):
+ self.testRenderCommentList("{% render_comment_list for a %}")
+
diff --git a/parts/django/tests/regressiontests/comment_tests/urls.py b/parts/django/tests/regressiontests/comment_tests/urls.py
new file mode 100644
index 0000000..0058689
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('regressiontests.comment_tests.custom_comments.views',
+ url(r'^post/$', 'custom_submit_comment'),
+ url(r'^flag/(\d+)/$', 'custom_flag_comment'),
+ url(r'^delete/(\d+)/$', 'custom_delete_comment'),
+ url(r'^approve/(\d+)/$', 'custom_approve_comment'),
+)
+
diff --git a/parts/django/tests/regressiontests/comment_tests/urls_admin.py b/parts/django/tests/regressiontests/comment_tests/urls_admin.py
new file mode 100644
index 0000000..d7e1a4e
--- /dev/null
+++ b/parts/django/tests/regressiontests/comment_tests/urls_admin.py
@@ -0,0 +1,19 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from django.contrib.comments.admin import CommentsAdmin
+from django.contrib.comments.models import Comment
+
+# Make a new AdminSite to avoid picking up the deliberately broken admin
+# modules in other tests.
+admin_site = admin.AdminSite()
+admin_site.register(Comment, CommentsAdmin)
+
+# To demonstrate proper functionality even when ``delete_selected`` is removed.
+admin_site2 = admin.AdminSite()
+admin_site2.disable_action('delete_selected')
+admin_site2.register(Comment, CommentsAdmin)
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin_site.urls)),
+ (r'^admin2/', include(admin_site2.urls)),
+)
diff --git a/parts/django/tests/regressiontests/conditional_processing/__init__.py b/parts/django/tests/regressiontests/conditional_processing/__init__.py
new file mode 100644
index 0000000..380474e
--- /dev/null
+++ b/parts/django/tests/regressiontests/conditional_processing/__init__.py
@@ -0,0 +1 @@
+# -*- coding:utf-8 -*-
diff --git a/parts/django/tests/regressiontests/conditional_processing/models.py b/parts/django/tests/regressiontests/conditional_processing/models.py
new file mode 100644
index 0000000..b291aed
--- /dev/null
+++ b/parts/django/tests/regressiontests/conditional_processing/models.py
@@ -0,0 +1,128 @@
+# -*- coding:utf-8 -*-
+from datetime import datetime, timedelta
+from calendar import timegm
+
+from django.test import TestCase
+from django.utils.http import parse_etags, quote_etag
+
+FULL_RESPONSE = 'Test conditional get response'
+LAST_MODIFIED = datetime(2007, 10, 21, 23, 21, 47)
+LAST_MODIFIED_STR = 'Sun, 21 Oct 2007 23:21:47 GMT'
+EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT'
+ETAG = 'b4246ffc4f62314ca13147c9d4f76974'
+EXPIRED_ETAG = '7fae4cd4b0f81e7d2914700043aa8ed6'
+
+class ConditionalGet(TestCase):
+ def assertFullResponse(self, response, check_last_modified=True, check_etag=True):
+ self.assertEquals(response.status_code, 200)
+ self.assertEquals(response.content, FULL_RESPONSE)
+ if check_last_modified:
+ self.assertEquals(response['Last-Modified'], LAST_MODIFIED_STR)
+ if check_etag:
+ self.assertEquals(response['ETag'], '"%s"' % ETAG)
+
+ def assertNotModified(self, response):
+ self.assertEquals(response.status_code, 304)
+ self.assertEquals(response.content, '')
+
+ def testWithoutConditions(self):
+ response = self.client.get('/condition/')
+ self.assertFullResponse(response)
+
+ def testIfModifiedSince(self):
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
+ response = self.client.get('/condition/')
+ self.assertNotModified(response)
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
+ response = self.client.get('/condition/')
+ self.assertFullResponse(response)
+
+ def testIfNoneMatch(self):
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % ETAG
+ response = self.client.get('/condition/')
+ self.assertNotModified(response)
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % EXPIRED_ETAG
+ response = self.client.get('/condition/')
+ self.assertFullResponse(response)
+
+ # Several etags in If-None-Match is a bit exotic but why not?
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s", "%s"' % (ETAG, EXPIRED_ETAG)
+ response = self.client.get('/condition/')
+ self.assertNotModified(response)
+
+ def testIfMatch(self):
+ self.client.defaults['HTTP_IF_MATCH'] = '"%s"' % ETAG
+ response = self.client.put('/condition/etag/', {'data': ''})
+ self.assertEquals(response.status_code, 200)
+ self.client.defaults['HTTP_IF_MATCH'] = '"%s"' % EXPIRED_ETAG
+ response = self.client.put('/condition/etag/', {'data': ''})
+ self.assertEquals(response.status_code, 412)
+
+ def testBothHeaders(self):
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % ETAG
+ response = self.client.get('/condition/')
+ self.assertNotModified(response)
+
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % ETAG
+ response = self.client.get('/condition/')
+ self.assertFullResponse(response)
+
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % EXPIRED_ETAG
+ response = self.client.get('/condition/')
+ self.assertFullResponse(response)
+
+ def testSingleCondition1(self):
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
+ response = self.client.get('/condition/last_modified/')
+ self.assertNotModified(response)
+ response = self.client.get('/condition/etag/')
+ self.assertFullResponse(response, check_last_modified=False)
+
+ def testSingleCondition2(self):
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % ETAG
+ response = self.client.get('/condition/etag/')
+ self.assertNotModified(response)
+ response = self.client.get('/condition/last_modified/')
+ self.assertFullResponse(response, check_etag=False)
+
+ def testSingleCondition3(self):
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
+ response = self.client.get('/condition/last_modified/')
+ self.assertFullResponse(response, check_etag=False)
+
+ def testSingleCondition4(self):
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % EXPIRED_ETAG
+ response = self.client.get('/condition/etag/')
+ self.assertFullResponse(response, check_last_modified=False)
+
+ def testSingleCondition5(self):
+ self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
+ response = self.client.get('/condition/last_modified2/')
+ self.assertNotModified(response)
+ response = self.client.get('/condition/etag2/')
+ self.assertFullResponse(response, check_last_modified=False)
+
+ def testSingleCondition6(self):
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % ETAG
+ response = self.client.get('/condition/etag2/')
+ self.assertNotModified(response)
+ response = self.client.get('/condition/last_modified2/')
+ self.assertFullResponse(response, check_etag=False)
+
+ def testInvalidETag(self):
+ self.client.defaults['HTTP_IF_NONE_MATCH'] = r'"\"'
+ response = self.client.get('/condition/etag/')
+ self.assertFullResponse(response, check_last_modified=False)
+
+
+class ETagProcesing(TestCase):
+ def testParsing(self):
+ etags = parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"')
+ self.assertEquals(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak'])
+
+ def testQuoting(self):
+ quoted_etag = quote_etag(r'e\t"ag')
+ self.assertEquals(quoted_etag, r'"e\\t\"ag"')
diff --git a/parts/django/tests/regressiontests/conditional_processing/urls.py b/parts/django/tests/regressiontests/conditional_processing/urls.py
new file mode 100644
index 0000000..4dbe11a
--- /dev/null
+++ b/parts/django/tests/regressiontests/conditional_processing/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import *
+import views
+
+urlpatterns = patterns('',
+ ('^$', views.index),
+ ('^last_modified/$', views.last_modified_view1),
+ ('^last_modified2/$', views.last_modified_view2),
+ ('^etag/$', views.etag_view1),
+ ('^etag2/$', views.etag_view2),
+)
diff --git a/parts/django/tests/regressiontests/conditional_processing/views.py b/parts/django/tests/regressiontests/conditional_processing/views.py
new file mode 100644
index 0000000..df49281
--- /dev/null
+++ b/parts/django/tests/regressiontests/conditional_processing/views.py
@@ -0,0 +1,26 @@
+# -*- coding:utf-8 -*-
+from django.views.decorators.http import condition, etag, last_modified
+from django.http import HttpResponse
+
+from models import FULL_RESPONSE, LAST_MODIFIED, ETAG
+
+def index(request):
+ return HttpResponse(FULL_RESPONSE)
+index = condition(lambda r: ETAG, lambda r: LAST_MODIFIED)(index)
+
+def last_modified_view1(request):
+ return HttpResponse(FULL_RESPONSE)
+last_modified_view1 = condition(last_modified_func=lambda r: LAST_MODIFIED)(last_modified_view1)
+
+def last_modified_view2(request):
+ return HttpResponse(FULL_RESPONSE)
+last_modified_view2 = last_modified(lambda r: LAST_MODIFIED)(last_modified_view2)
+
+def etag_view1(request):
+ return HttpResponse(FULL_RESPONSE)
+etag_view1 = condition(etag_func=lambda r: ETAG)(etag_view1)
+
+def etag_view2(request):
+ return HttpResponse(FULL_RESPONSE)
+etag_view2 = etag(lambda r: ETAG)(etag_view2)
+
diff --git a/parts/django/tests/regressiontests/context_processors/__init__.py b/parts/django/tests/regressiontests/context_processors/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/__init__.py
diff --git a/parts/django/tests/regressiontests/context_processors/fixtures/context-processors-users.xml b/parts/django/tests/regressiontests/context_processors/fixtures/context-processors-users.xml
new file mode 100644
index 0000000..aba8f4a
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/fixtures/context-processors-users.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/context_processors/models.py b/parts/django/tests/regressiontests/context_processors/models.py
new file mode 100644
index 0000000..cde172d
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/models.py
@@ -0,0 +1 @@
+# Models file for tests to run.
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
new file mode 100644
index 0000000..b5c65db
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
@@ -0,0 +1 @@
+{{ user }}
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
new file mode 100644
index 0000000..7b7e448
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
@@ -0,0 +1 @@
+{% for m in messages %}{{ m }}{% endfor %}
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
new file mode 100644
index 0000000..a5db868
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
@@ -0,0 +1 @@
+{% if perms.auth %}Has auth permissions{% endif %}
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
new file mode 100644
index 0000000..a28ff93
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
@@ -0,0 +1 @@
+{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html
new file mode 100644
index 0000000..7ed16d7
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html
@@ -0,0 +1,4 @@
+unicode: {{ user }}
+id: {{ user.id }}
+username: {{ user.username }}
+url: {% url userpage user %}
diff --git a/parts/django/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html b/parts/django/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html
new file mode 100644
index 0000000..3978e9d
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html
@@ -0,0 +1,13 @@
+{% if request %}
+Have request
+{% else %}
+No request
+{% endif %}
+
+{% if request.is_secure %}
+Secure
+{% else %}
+Not secure
+{% endif %}
+
+{{ request.path }}
diff --git a/parts/django/tests/regressiontests/context_processors/tests.py b/parts/django/tests/regressiontests/context_processors/tests.py
new file mode 100644
index 0000000..0d19bef
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/tests.py
@@ -0,0 +1,112 @@
+"""
+Tests for Django's bundled context processors.
+"""
+
+from django.conf import settings
+from django.contrib.auth import authenticate
+from django.db.models import Q
+from django.test import TestCase
+from django.template import Template
+
+class RequestContextProcessorTests(TestCase):
+ """
+ Tests for the ``django.core.context_processors.request`` processor.
+ """
+
+ urls = 'regressiontests.context_processors.urls'
+
+ def test_request_attributes(self):
+ """
+ Test that the request object is available in the template and that its
+ attributes can't be overridden by GET and POST parameters (#3828).
+ """
+ url = '/request_attrs/'
+ # We should have the request object in the template.
+ response = self.client.get(url)
+ self.assertContains(response, 'Have request')
+ # Test is_secure.
+ response = self.client.get(url)
+ self.assertContains(response, 'Not secure')
+ response = self.client.get(url, {'is_secure': 'blah'})
+ self.assertContains(response, 'Not secure')
+ response = self.client.post(url, {'is_secure': 'blah'})
+ self.assertContains(response, 'Not secure')
+ # Test path.
+ response = self.client.get(url)
+ self.assertContains(response, url)
+ response = self.client.get(url, {'path': '/blah/'})
+ self.assertContains(response, url)
+ response = self.client.post(url, {'path': '/blah/'})
+ self.assertContains(response, url)
+
+class AuthContextProcessorTests(TestCase):
+ """
+ Tests for the ``django.contrib.auth.context_processors.auth`` processor
+ """
+ urls = 'regressiontests.context_processors.urls'
+ fixtures = ['context-processors-users.xml']
+
+ def test_session_not_accessed(self):
+ """
+ Tests that the session is not accessed simply by including
+ the auth context processor
+ """
+ response = self.client.get('/auth_processor_no_attr_access/')
+ self.assertContains(response, "Session not accessed")
+
+ def test_session_is_accessed(self):
+ """
+ Tests that the session is accessed if the auth context processor
+ is used and relevant attributes accessed.
+ """
+ response = self.client.get('/auth_processor_attr_access/')
+ self.assertContains(response, "Session accessed")
+
+ def test_perms_attrs(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_perms/')
+ self.assertContains(response, "Has auth permissions")
+
+ def test_message_attrs(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_messages/')
+ self.assertContains(response, "Message 1")
+
+ def test_user_attrs(self):
+ """
+ Test that the lazy objects returned behave just like the wrapped objects.
+ """
+ # These are 'functional' level tests for common use cases. Direct
+ # testing of the implementation (SimpleLazyObject) is in the 'utils'
+ # tests.
+ self.client.login(username='super', password='secret')
+ user = authenticate(username='super', password='secret')
+ response = self.client.get('/auth_processor_user/')
+ self.assertContains(response, "unicode: super")
+ self.assertContains(response, "id: 100")
+ self.assertContains(response, "username: super")
+ # bug #12037 is tested by the {% url %} in the template:
+ self.assertContains(response, "url: /userpage/super/")
+
+ # See if this object can be used for queries where a Q() comparing
+ # a user can be used with another Q() (in an AND or OR fashion).
+ # This simulates what a template tag might do with the user from the
+ # context. Note that we don't need to execute a query, just build it.
+ #
+ # The failure case (bug #12049) on Python 2.4 with a LazyObject-wrapped
+ # User is a fatal TypeError: "function() takes at least 2 arguments
+ # (0 given)" deep inside deepcopy().
+ #
+ # Python 2.5 and 2.6 succeeded, but logged internally caught exception
+ # spew:
+ #
+ # Exception RuntimeError: 'maximum recursion depth exceeded while
+ # calling a Python object' in <type 'exceptions.AttributeError'>
+ # ignored"
+ query = Q(user=response.context['user']) & Q(someflag=True)
+
+ # Tests for user equality. This is hard because User defines
+ # equality in a non-duck-typing way
+ # See bug #12060
+ self.assertEqual(response.context['user'], user)
+ self.assertEqual(user, response.context['user'])
diff --git a/parts/django/tests/regressiontests/context_processors/urls.py b/parts/django/tests/regressiontests/context_processors/urls.py
new file mode 100644
index 0000000..30728c8
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/urls.py
@@ -0,0 +1,14 @@
+from django.conf.urls.defaults import *
+
+import views
+
+
+urlpatterns = patterns('',
+ (r'^request_attrs/$', views.request_processor),
+ (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
+ (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
+ (r'^auth_processor_user/$', views.auth_processor_user),
+ (r'^auth_processor_perms/$', views.auth_processor_perms),
+ (r'^auth_processor_messages/$', views.auth_processor_messages),
+ url(r'^userpage/(.+)/$', views.userpage, name="userpage"),
+)
diff --git a/parts/django/tests/regressiontests/context_processors/views.py b/parts/django/tests/regressiontests/context_processors/views.py
new file mode 100644
index 0000000..3f2dcb0
--- /dev/null
+++ b/parts/django/tests/regressiontests/context_processors/views.py
@@ -0,0 +1,37 @@
+from django.core import context_processors
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+
+
+def request_processor(request):
+ return render_to_response('context_processors/request_attrs.html',
+ RequestContext(request, {}, processors=[context_processors.request]))
+
+def auth_processor_no_attr_access(request):
+ r1 = render_to_response('context_processors/auth_attrs_no_access.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+ # *After* rendering, we check whether the session was accessed
+ return render_to_response('context_processors/auth_attrs_test_access.html',
+ {'session_accessed':request.session.accessed})
+
+def auth_processor_attr_access(request):
+ r1 = render_to_response('context_processors/auth_attrs_access.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+ return render_to_response('context_processors/auth_attrs_test_access.html',
+ {'session_accessed':request.session.accessed})
+
+def auth_processor_user(request):
+ return render_to_response('context_processors/auth_attrs_user.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_perms(request):
+ return render_to_response('context_processors/auth_attrs_perms.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_messages(request):
+ request.user.message_set.create(message="Message 1")
+ return render_to_response('context_processors/auth_attrs_messages.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def userpage(request):
+ pass
diff --git a/parts/django/tests/regressiontests/csrf_tests/__init__.py b/parts/django/tests/regressiontests/csrf_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/csrf_tests/__init__.py
diff --git a/parts/django/tests/regressiontests/csrf_tests/models.py b/parts/django/tests/regressiontests/csrf_tests/models.py
new file mode 100644
index 0000000..71abcc5
--- /dev/null
+++ b/parts/django/tests/regressiontests/csrf_tests/models.py
@@ -0,0 +1 @@
+# models.py file for tests to run.
diff --git a/parts/django/tests/regressiontests/csrf_tests/tests.py b/parts/django/tests/regressiontests/csrf_tests/tests.py
new file mode 100644
index 0000000..9f74fc5
--- /dev/null
+++ b/parts/django/tests/regressiontests/csrf_tests/tests.py
@@ -0,0 +1,375 @@
+# -*- coding: utf-8 -*-
+
+from django.test import TestCase
+from django.http import HttpRequest, HttpResponse
+from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware
+from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, requires_csrf_token
+from django.core.context_processors import csrf
+from django.contrib.sessions.middleware import SessionMiddleware
+from django.utils.importlib import import_module
+from django.conf import settings
+from django.template import RequestContext, Template
+
+# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests
+def post_form_response():
+ resp = HttpResponse(content=u"""
+<html><body><h1>\u00a1Unicode!<form method="post"><input type="text" /></form></body></html>
+""", mimetype="text/html")
+ return resp
+
+def post_form_response_non_html():
+ resp = post_form_response()
+ resp["Content-Type"] = "application/xml"
+ return resp
+
+def post_form_view(request):
+ """A view that returns a POST form (without a token)"""
+ return post_form_response()
+
+# Response/views used for template tag tests
+def _token_template():
+ return Template("{% csrf_token %}")
+
+def _render_csrf_token_template(req):
+ context = RequestContext(req, processors=[csrf])
+ template = _token_template()
+ return template.render(context)
+
+def token_view(request):
+ """A view that uses {% csrf_token %}"""
+ return HttpResponse(_render_csrf_token_template(request))
+
+def non_token_view_using_request_processor(request):
+ """
+ A view that doesn't use the token, but does use the csrf view processor.
+ """
+ context = RequestContext(request, processors=[csrf])
+ template = Template("")
+ return HttpResponse(template.render(context))
+
+class TestingHttpRequest(HttpRequest):
+ """
+ A version of HttpRequest that allows us to change some things
+ more easily
+ """
+ def is_secure(self):
+ return getattr(self, '_is_secure', False)
+
+class CsrfMiddlewareTest(TestCase):
+ # The csrf token is potentially from an untrusted source, so could have
+ # characters that need dealing with.
+ _csrf_id_cookie = "<1>\xc2\xa1"
+ _csrf_id = "1"
+
+ # This is a valid session token for this ID and secret key. This was generated using
+ # the old code that we're to be backwards-compatible with. Don't use the CSRF code
+ # to generate this hash, or we're merely testing the code against itself and not
+ # checking backwards-compatibility. This is also the output of (echo -n test1 | md5sum).
+ _session_token = "5a105e8b9d40e1329780d62ea2265d8a"
+ _session_id = "1"
+ _secret_key_for_session_test= "test"
+
+ def _get_GET_no_csrf_cookie_request(self):
+ return TestingHttpRequest()
+
+ def _get_GET_csrf_cookie_request(self):
+ req = TestingHttpRequest()
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie
+ return req
+
+ def _get_POST_csrf_cookie_request(self):
+ req = self._get_GET_csrf_cookie_request()
+ req.method = "POST"
+ return req
+
+ def _get_POST_no_csrf_cookie_request(self):
+ req = self._get_GET_no_csrf_cookie_request()
+ req.method = "POST"
+ return req
+
+ def _get_POST_request_with_token(self):
+ req = self._get_POST_csrf_cookie_request()
+ req.POST['csrfmiddlewaretoken'] = self._csrf_id
+ return req
+
+ def _get_POST_session_request_with_token(self):
+ req = self._get_POST_no_csrf_cookie_request()
+ req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
+ req.POST['csrfmiddlewaretoken'] = self._session_token
+ return req
+
+ def _get_POST_session_request_no_token(self):
+ req = self._get_POST_no_csrf_cookie_request()
+ req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
+ return req
+
+ def _check_token_present(self, response, csrf_id=None):
+ self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id))
+
+ # Check the post processing and outgoing cookie
+ def test_process_response_no_csrf_cookie(self):
+ """
+ When no prior CSRF cookie exists, check that the cookie is created and a
+ token is inserted.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, post_form_view, (), {})
+
+ resp = post_form_response()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertNotEqual(csrf_cookie, False)
+ self.assertNotEqual(resp_content, resp2.content)
+ self._check_token_present(resp2, csrf_cookie.value)
+ # Check the Vary header got patched correctly
+ self.assert_('Cookie' in resp2.get('Vary',''))
+
+ def test_process_response_for_exempt_view(self):
+ """
+ Check that a view decorated with 'csrf_view_exempt' is still
+ post-processed to add the CSRF token.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, csrf_view_exempt(post_form_view), (), {})
+
+ resp = post_form_response()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertNotEqual(csrf_cookie, False)
+ self.assertNotEqual(resp_content, resp2.content)
+ self._check_token_present(resp2, csrf_cookie.value)
+
+ def test_process_response_no_csrf_cookie_view_only_get_token_used(self):
+ """
+ When no prior CSRF cookie exists, check that the cookie is created, even
+ if only CsrfViewMiddleware is used.
+ """
+ # This is checking that CsrfViewMiddleware has the cookie setting
+ # code. Most of the other tests use CsrfMiddleware.
+ req = self._get_GET_no_csrf_cookie_request()
+ # token_view calls get_token() indirectly
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertNotEqual(csrf_cookie, False)
+
+ def test_process_response_get_token_not_used(self):
+ """
+ Check that if get_token() is not called, the view middleware does not
+ add a cookie.
+ """
+ # This is important to make pages cacheable. Pages which do call
+ # get_token(), assuming they use the token, are not cacheable because
+ # the token is specific to the user
+ req = self._get_GET_no_csrf_cookie_request()
+ # non_token_view_using_request_processor does not call get_token(), but
+ # does use the csrf request processor. By using this, we are testing
+ # that the view processor is properly lazy and doesn't call get_token()
+ # until needed.
+ CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {})
+ resp = non_token_view_using_request_processor(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertEqual(csrf_cookie, False)
+
+ def test_process_response_existing_csrf_cookie(self):
+ """
+ Check that the token is inserted when a prior CSRF cookie exists
+ """
+ req = self._get_GET_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, post_form_view, (), {})
+
+ resp = post_form_response()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+ self.assertNotEqual(resp_content, resp2.content)
+ self._check_token_present(resp2)
+
+ def test_process_response_non_html(self):
+ """
+ Check the the post-processor does nothing for content-types not in _HTML_TYPES.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfMiddleware().process_view(req, post_form_view, (), {})
+ resp = post_form_response_non_html()
+ resp_content = resp.content # needed because process_response modifies resp
+ resp2 = CsrfMiddleware().process_response(req, resp)
+ self.assertEquals(resp_content, resp2.content)
+
+ def test_process_response_exempt_view(self):
+ """
+ Check that no post processing is done for an exempt view
+ """
+ req = self._get_GET_csrf_cookie_request()
+ view = csrf_exempt(post_form_view)
+ CsrfMiddleware().process_view(req, view, (), {})
+
+ resp = view(req)
+ resp_content = resp.content
+ resp2 = CsrfMiddleware().process_response(req, resp)
+ self.assertEquals(resp_content, resp2.content)
+
+ # Check the request processing
+ def test_process_request_no_session_no_csrf_cookie(self):
+ """
+ Check that if neither a CSRF cookie nor a session cookie are present,
+ the middleware rejects the incoming request. This will stop login CSRF.
+ """
+ req = self._get_POST_no_csrf_cookie_request()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(403, req2.status_code)
+
+ def test_process_request_csrf_cookie_no_token(self):
+ """
+ Check that if a CSRF cookie is present but no token, the middleware
+ rejects the incoming request.
+ """
+ req = self._get_POST_csrf_cookie_request()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(403, req2.status_code)
+
+ def test_process_request_csrf_cookie_and_token(self):
+ """
+ Check that if both a cookie and a token is present, the middleware lets it through.
+ """
+ req = self._get_POST_request_with_token()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
+
+ def test_process_request_session_cookie_no_csrf_cookie_token(self):
+ """
+ When no CSRF cookie exists, but the user has a session, check that a token
+ using the session cookie as a legacy CSRF cookie is accepted.
+ """
+ orig_secret_key = settings.SECRET_KEY
+ settings.SECRET_KEY = self._secret_key_for_session_test
+ try:
+ req = self._get_POST_session_request_with_token()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
+ finally:
+ settings.SECRET_KEY = orig_secret_key
+
+ def test_process_request_session_cookie_no_csrf_cookie_no_token(self):
+ """
+ Check that if a session cookie is present but no token and no CSRF cookie,
+ the request is rejected.
+ """
+ req = self._get_POST_session_request_no_token()
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(403, req2.status_code)
+
+ def test_process_request_csrf_cookie_no_token_exempt_view(self):
+ """
+ Check that if a CSRF cookie is present and no token, but the csrf_exempt
+ decorator has been applied to the view, the middleware lets it through
+ """
+ req = self._get_POST_csrf_cookie_request()
+ req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {})
+ self.assertEquals(None, req2)
+
+ def test_ajax_exemption(self):
+ """
+ Check that AJAX requests are automatically exempted.
+ """
+ req = self._get_POST_csrf_cookie_request()
+ req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
+
+ # Tests for the template tag method
+ def test_token_node_no_csrf_cookie(self):
+ """
+ Check that CsrfTokenNode works when no CSRF cookie is set
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ resp = token_view(req)
+ self.assertEquals(u"", resp.content)
+
+ def test_token_node_empty_csrf_cookie(self):
+ """
+ Check that we get a new token if the csrf_cookie is the empty string
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = ""
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+
+ self.assertNotEqual(u"", resp.content)
+
+ def test_token_node_with_csrf_cookie(self):
+ """
+ Check that CsrfTokenNode works when a CSRF cookie is set
+ """
+ req = self._get_GET_csrf_cookie_request()
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ self._check_token_present(resp)
+
+ def test_get_token_for_exempt_view(self):
+ """
+ Check that get_token still works for a view decorated with 'csrf_view_exempt'.
+ """
+ req = self._get_GET_csrf_cookie_request()
+ CsrfViewMiddleware().process_view(req, csrf_view_exempt(token_view), (), {})
+ resp = token_view(req)
+ self._check_token_present(resp)
+
+ def test_get_token_for_requires_csrf_token_view(self):
+ """
+ Check that get_token works for a view decorated solely with requires_csrf_token
+ """
+ req = self._get_GET_csrf_cookie_request()
+ resp = requires_csrf_token(token_view)(req)
+ self._check_token_present(resp)
+
+ def test_token_node_with_new_csrf_cookie(self):
+ """
+ Check that CsrfTokenNode works when a CSRF cookie is created by
+ the middleware (when one was not already present)
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+ csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
+ self._check_token_present(resp, csrf_id=csrf_cookie.value)
+
+ def test_response_middleware_without_view_middleware(self):
+ """
+ Check that CsrfResponseMiddleware finishes without error if the view middleware
+ has not been called, as is the case if a request middleware returns a response.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ resp = post_form_view(req)
+ CsrfMiddleware().process_response(req, resp)
+
+ def test_https_bad_referer(self):
+ """
+ Test that a POST HTTPS request with a bad referer is rejected
+ """
+ req = self._get_POST_request_with_token()
+ req._is_secure = True
+ req.META['HTTP_HOST'] = 'www.example.com'
+ req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage'
+ req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
+ self.assertNotEqual(None, req2)
+ self.assertEquals(403, req2.status_code)
+
+ def test_https_good_referer(self):
+ """
+ Test that a POST HTTPS request with a good referer is accepted
+ """
+ req = self._get_POST_request_with_token()
+ req._is_secure = True
+ req.META['HTTP_HOST'] = 'www.example.com'
+ req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
+ req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
+ self.assertEquals(None, req2)
diff --git a/parts/django/tests/regressiontests/custom_columns_regress/__init__.py b/parts/django/tests/regressiontests/custom_columns_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/custom_columns_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/custom_columns_regress/models.py b/parts/django/tests/regressiontests/custom_columns_regress/models.py
new file mode 100644
index 0000000..93de237
--- /dev/null
+++ b/parts/django/tests/regressiontests/custom_columns_regress/models.py
@@ -0,0 +1,36 @@
+"""
+Regression for #9736.
+
+Checks some pathological column naming to make sure it doesn't break
+table creation or queries.
+
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ Article_ID = models.AutoField(primary_key=True, db_column='Article ID')
+ headline = models.CharField(max_length=100)
+ authors = models.ManyToManyField('Author', db_table='my m2m table')
+ primary_author = models.ForeignKey('Author', db_column='Author ID', related_name='primary_set')
+
+ def __unicode__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+class Author(models.Model):
+ Author_ID = models.AutoField(primary_key=True, db_column='Author ID')
+ first_name = models.CharField(max_length=30, db_column='first name')
+ last_name = models.CharField(max_length=30, db_column='last name')
+
+ def __unicode__(self):
+ return u'%s %s' % (self.first_name, self.last_name)
+
+ class Meta:
+ db_table = 'my author table'
+ ordering = ('last_name','first_name')
+
+
+
diff --git a/parts/django/tests/regressiontests/custom_columns_regress/tests.py b/parts/django/tests/regressiontests/custom_columns_regress/tests.py
new file mode 100644
index 0000000..8507601
--- /dev/null
+++ b/parts/django/tests/regressiontests/custom_columns_regress/tests.py
@@ -0,0 +1,84 @@
+from django.test import TestCase
+from django.core.exceptions import FieldError
+
+from models import Author, Article
+
+def pks(objects):
+ """ Return pks to be able to compare lists"""
+ return [o.pk for o in objects]
+
+class CustomColumnRegression(TestCase):
+
+ def assertRaisesMessage(self, exc, msg, func, *args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except Exception, e:
+ self.assertEqual(msg, str(e))
+ self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e)))
+
+ def setUp(self):
+ self.a1 = Author.objects.create(first_name='John', last_name='Smith')
+ self.a2 = Author.objects.create(first_name='Peter', last_name='Jones')
+ self.authors = [self.a1, self.a2]
+
+ def test_basic_creation(self):
+ art = Article(headline='Django lets you build Web apps easily', primary_author=self.a1)
+ art.save()
+ art.authors = [self.a1, self.a2]
+
+ def test_author_querying(self):
+ self.assertQuerysetEqual(
+ Author.objects.all().order_by('last_name'),
+ ['<Author: Peter Jones>', '<Author: John Smith>']
+ )
+
+ def test_author_filtering(self):
+ self.assertQuerysetEqual(
+ Author.objects.filter(first_name__exact='John'),
+ ['<Author: John Smith>']
+ )
+
+ def test_author_get(self):
+ self.assertEqual(self.a1, Author.objects.get(first_name__exact='John'))
+
+ def test_filter_on_nonexistant_field(self):
+ self.assertRaisesMessage(
+ FieldError,
+ "Cannot resolve keyword 'firstname' into field. Choices are: Author_ID, article, first_name, last_name, primary_set",
+ Author.objects.filter,
+ firstname__exact='John'
+ )
+
+ def test_author_get_attributes(self):
+ a = Author.objects.get(last_name__exact='Smith')
+ self.assertEqual('John', a.first_name)
+ self.assertEqual('Smith', a.last_name)
+ self.assertRaisesMessage(
+ AttributeError,
+ "'Author' object has no attribute 'firstname'",
+ getattr,
+ a, 'firstname'
+ )
+
+ self.assertRaisesMessage(
+ AttributeError,
+ "'Author' object has no attribute 'last'",
+ getattr,
+ a, 'last'
+ )
+
+ def test_m2m_table(self):
+ art = Article.objects.create(headline='Django lets you build Web apps easily', primary_author=self.a1)
+ art.authors = self.authors
+ self.assertQuerysetEqual(
+ art.authors.all().order_by('last_name'),
+ ['<Author: Peter Jones>', '<Author: John Smith>']
+ )
+ self.assertQuerysetEqual(
+ self.a1.article_set.all(),
+ ['<Article: Django lets you build Web apps easily>']
+ )
+ self.assertQuerysetEqual(
+ art.authors.filter(last_name='Jones'),
+ ['<Author: Peter Jones>']
+ )
diff --git a/parts/django/tests/regressiontests/custom_managers_regress/__init__.py b/parts/django/tests/regressiontests/custom_managers_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/custom_managers_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/custom_managers_regress/models.py b/parts/django/tests/regressiontests/custom_managers_regress/models.py
new file mode 100644
index 0000000..747972b
--- /dev/null
+++ b/parts/django/tests/regressiontests/custom_managers_regress/models.py
@@ -0,0 +1,40 @@
+"""
+Regression tests for custom manager classes.
+"""
+
+from django.db import models
+
+class RestrictedManager(models.Manager):
+ """
+ A manager that filters out non-public instances.
+ """
+ def get_query_set(self):
+ return super(RestrictedManager, self).get_query_set().filter(is_public=True)
+
+class RelatedModel(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return self.name
+
+class RestrictedModel(models.Model):
+ name = models.CharField(max_length=50)
+ is_public = models.BooleanField(default=False)
+ related = models.ForeignKey(RelatedModel)
+
+ objects = RestrictedManager()
+ plain_manager = models.Manager()
+
+ def __unicode__(self):
+ return self.name
+
+class OneToOneRestrictedModel(models.Model):
+ name = models.CharField(max_length=50)
+ is_public = models.BooleanField(default=False)
+ related = models.OneToOneField(RelatedModel)
+
+ objects = RestrictedManager()
+ plain_manager = models.Manager()
+
+ def __unicode__(self):
+ return self.name
diff --git a/parts/django/tests/regressiontests/custom_managers_regress/tests.py b/parts/django/tests/regressiontests/custom_managers_regress/tests.py
new file mode 100644
index 0000000..6dd668a
--- /dev/null
+++ b/parts/django/tests/regressiontests/custom_managers_regress/tests.py
@@ -0,0 +1,47 @@
+from django.test import TestCase
+
+from models import RelatedModel, RestrictedModel, OneToOneRestrictedModel
+
+class CustomManagersRegressTestCase(TestCase):
+ def test_filtered_default_manager(self):
+ """Even though the default manager filters out some records,
+ we must still be able to save (particularly, save by updating
+ existing records) those filtered instances. This is a
+ regression test for #8990, #9527"""
+ related = RelatedModel.objects.create(name="xyzzy")
+ obj = RestrictedModel.objects.create(name="hidden", related=related)
+ obj.name = "still hidden"
+ obj.save()
+
+ # If the hidden object wasn't seen during the save process,
+ # there would now be two objects in the database.
+ self.assertEqual(RestrictedModel.plain_manager.count(), 1)
+
+ def test_delete_related_on_filtered_manager(self):
+ """Deleting related objects should also not be distracted by a
+ restricted manager on the related object. This is a regression
+ test for #2698."""
+ related = RelatedModel.objects.create(name="xyzzy")
+
+ for name, public in (('one', True), ('two', False), ('three', False)):
+ RestrictedModel.objects.create(name=name, is_public=public, related=related)
+
+ obj = RelatedModel.objects.get(name="xyzzy")
+ obj.delete()
+
+ # All of the RestrictedModel instances should have been
+ # deleted, since they *all* pointed to the RelatedModel. If
+ # the default manager is used, only the public one will be
+ # deleted.
+ self.assertEqual(len(RestrictedModel.plain_manager.all()), 0)
+
+ def test_delete_one_to_one_manager(self):
+ # The same test case as the last one, but for one-to-one
+ # models, which are implemented slightly different internally,
+ # so it's a different code path.
+ obj = RelatedModel.objects.create(name="xyzzy")
+ OneToOneRestrictedModel.objects.create(name="foo", is_public=False, related=obj)
+ obj = RelatedModel.objects.get(name="xyzzy")
+ obj.delete()
+ self.assertEqual(len(OneToOneRestrictedModel.plain_manager.all()), 0)
+
diff --git a/parts/django/tests/regressiontests/datatypes/__init__.py b/parts/django/tests/regressiontests/datatypes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/datatypes/__init__.py
diff --git a/parts/django/tests/regressiontests/datatypes/models.py b/parts/django/tests/regressiontests/datatypes/models.py
new file mode 100644
index 0000000..f6fb72d
--- /dev/null
+++ b/parts/django/tests/regressiontests/datatypes/models.py
@@ -0,0 +1,25 @@
+"""
+This is a basic model to test saving and loading boolean and date-related
+types, which in the past were problematic for some database backends.
+"""
+
+from django.db import models
+
+class Donut(models.Model):
+ name = models.CharField(max_length=100)
+ is_frosted = models.BooleanField(default=False)
+ has_sprinkles = models.NullBooleanField()
+ baked_date = models.DateField(null=True)
+ baked_time = models.TimeField(null=True)
+ consumed_at = models.DateTimeField(null=True)
+ review = models.TextField()
+
+ class Meta:
+ ordering = ('consumed_at',)
+
+ def __str__(self):
+ return self.name
+
+class RumBaba(models.Model):
+ baked_date = models.DateField(auto_now_add=True)
+ baked_timestamp = models.DateTimeField(auto_now_add=True)
diff --git a/parts/django/tests/regressiontests/datatypes/tests.py b/parts/django/tests/regressiontests/datatypes/tests.py
new file mode 100644
index 0000000..f7a0447
--- /dev/null
+++ b/parts/django/tests/regressiontests/datatypes/tests.py
@@ -0,0 +1,93 @@
+import datetime
+from django.db import DEFAULT_DB_ALIAS
+from django.test import TestCase
+from django.utils import tzinfo
+
+from models import Donut, RumBaba
+from django.conf import settings
+
+class DataTypesTestCase(TestCase):
+
+ def test_boolean_type(self):
+ d = Donut(name='Apple Fritter')
+ self.assertFalse(d.is_frosted)
+ self.assertTrue(d.has_sprinkles is None)
+ d.has_sprinkles = True
+ self.assertTrue(d.has_sprinkles)
+
+ d.save()
+
+ d2 = Donut.objects.get(name='Apple Fritter')
+ self.assertFalse(d2.is_frosted)
+ self.assertTrue(d2.has_sprinkles)
+
+ def test_date_type(self):
+ d = Donut(name='Apple Fritter')
+ d.baked_date = datetime.date(year=1938, month=6, day=4)
+ d.baked_time = datetime.time(hour=5, minute=30)
+ d.consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59)
+ d.save()
+
+ d2 = Donut.objects.get(name='Apple Fritter')
+ self.assertEqual(d2.baked_date, datetime.date(1938, 6, 4))
+ self.assertEqual(d2.baked_time, datetime.time(5, 30))
+ self.assertEqual(d2.consumed_at, datetime.datetime(2007, 4, 20, 16, 19, 59))
+
+ def test_time_field(self):
+ #Test for ticket #12059: TimeField wrongly handling datetime.datetime object.
+ d = Donut(name='Apple Fritter')
+ d.baked_time = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59)
+ d.save()
+
+ d2 = Donut.objects.get(name='Apple Fritter')
+ self.assertEqual(d2.baked_time, datetime.time(16, 19, 59))
+
+ def test_year_boundaries(self):
+ """Year boundary tests (ticket #3689)"""
+ d = Donut.objects.create(name='Date Test 2007',
+ baked_date=datetime.datetime(year=2007, month=12, day=31),
+ consumed_at=datetime.datetime(year=2007, month=12, day=31, hour=23, minute=59, second=59))
+ d1 = Donut.objects.create(name='Date Test 2006',
+ baked_date=datetime.datetime(year=2006, month=1, day=1),
+ consumed_at=datetime.datetime(year=2006, month=1, day=1))
+
+ self.assertEqual("Date Test 2007",
+ Donut.objects.filter(baked_date__year=2007)[0].name)
+
+ self.assertEqual("Date Test 2006",
+ Donut.objects.filter(baked_date__year=2006)[0].name)
+
+ d2 = Donut.objects.create(name='Apple Fritter',
+ consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59))
+
+ self.assertEqual([u'Apple Fritter', u'Date Test 2007'],
+ list(Donut.objects.filter(consumed_at__year=2007).order_by('name').values_list('name', flat=True)))
+
+ self.assertEqual(0, Donut.objects.filter(consumed_at__year=2005).count())
+ self.assertEqual(0, Donut.objects.filter(consumed_at__year=2008).count())
+
+ def test_textfields_unicode(self):
+ """Regression test for #10238: TextField values returned from the
+ database should be unicode."""
+ d = Donut.objects.create(name=u'Jelly Donut', review=u'Outstanding')
+ newd = Donut.objects.get(id=d.id)
+ self.assert_(isinstance(newd.review, unicode))
+
+ def test_tz_awareness_mysql(self):
+ """Regression test for #8354: the MySQL backend should raise an error
+ if given a timezone-aware datetime object."""
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql':
+ dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0))
+ d = Donut(name='Bear claw', consumed_at=dt)
+ self.assertRaises(ValueError, d.save)
+ # ValueError: MySQL backend does not support timezone-aware datetimes.
+
+ def test_datefield_auto_now_add(self):
+ """Regression test for #10970, auto_now_add for DateField should store
+ a Python datetime.date, not a datetime.datetime"""
+ b = RumBaba.objects.create()
+ # Verify we didn't break DateTimeField behavior
+ self.assert_(isinstance(b.baked_timestamp, datetime.datetime))
+ # We need to test this this way because datetime.datetime inherits
+ # from datetime.date:
+ self.assert_(isinstance(b.baked_date, datetime.date) and not isinstance(b.baked_date, datetime.datetime))
diff --git a/parts/django/tests/regressiontests/db_typecasts/__init__.py b/parts/django/tests/regressiontests/db_typecasts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/db_typecasts/__init__.py
diff --git a/parts/django/tests/regressiontests/db_typecasts/models.py b/parts/django/tests/regressiontests/db_typecasts/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/db_typecasts/models.py
diff --git a/parts/django/tests/regressiontests/db_typecasts/tests.py b/parts/django/tests/regressiontests/db_typecasts/tests.py
new file mode 100644
index 0000000..8c71c8f
--- /dev/null
+++ b/parts/django/tests/regressiontests/db_typecasts/tests.py
@@ -0,0 +1,62 @@
+# Unit tests for typecast functions in django.db.backends.util
+
+from django.db.backends import util as typecasts
+import datetime, unittest
+
+TEST_CASES = {
+ 'typecast_date': (
+ ('', None),
+ (None, None),
+ ('2005-08-11', datetime.date(2005, 8, 11)),
+ ('1990-01-01', datetime.date(1990, 1, 1)),
+ ),
+ 'typecast_time': (
+ ('', None),
+ (None, None),
+ ('0:00:00', datetime.time(0, 0)),
+ ('0:30:00', datetime.time(0, 30)),
+ ('8:50:00', datetime.time(8, 50)),
+ ('08:50:00', datetime.time(8, 50)),
+ ('12:00:00', datetime.time(12, 00)),
+ ('12:30:00', datetime.time(12, 30)),
+ ('13:00:00', datetime.time(13, 00)),
+ ('23:59:00', datetime.time(23, 59)),
+ ('00:00:12', datetime.time(0, 0, 12)),
+ ('00:00:12.5', datetime.time(0, 0, 12, 500000)),
+ ('7:22:13.312', datetime.time(7, 22, 13, 312000)),
+ ),
+ 'typecast_timestamp': (
+ ('', None),
+ (None, None),
+ ('2005-08-11 0:00:00', datetime.datetime(2005, 8, 11)),
+ ('2005-08-11 0:30:00', datetime.datetime(2005, 8, 11, 0, 30)),
+ ('2005-08-11 8:50:30', datetime.datetime(2005, 8, 11, 8, 50, 30)),
+ ('2005-08-11 8:50:30.123', datetime.datetime(2005, 8, 11, 8, 50, 30, 123000)),
+ ('2005-08-11 8:50:30.9', datetime.datetime(2005, 8, 11, 8, 50, 30, 900000)),
+ ('2005-08-11 8:50:30.312-05', datetime.datetime(2005, 8, 11, 8, 50, 30, 312000)),
+ ('2005-08-11 8:50:30.312+02', datetime.datetime(2005, 8, 11, 8, 50, 30, 312000)),
+ # ticket 14453
+ ('2010-10-12 15:29:22.063202', datetime.datetime(2010, 10, 12, 15, 29, 22, 63202)),
+ ('2010-10-12 15:29:22.063202-03', datetime.datetime(2010, 10, 12, 15, 29, 22, 63202)),
+ ('2010-10-12 15:29:22.063202+04', datetime.datetime(2010, 10, 12, 15, 29, 22, 63202)),
+ ('2010-10-12 15:29:22.0632021', datetime.datetime(2010, 10, 12, 15, 29, 22, 63202)),
+ ('2010-10-12 15:29:22.0632029', datetime.datetime(2010, 10, 12, 15, 29, 22, 63202)),
+ ),
+ 'typecast_boolean': (
+ (None, None),
+ ('', False),
+ ('t', True),
+ ('f', False),
+ ('x', False),
+ ),
+}
+
+class DBTypeCasts(unittest.TestCase):
+ def test_typeCasts(self):
+ for k, v in TEST_CASES.items():
+ for inpt, expected in v:
+ got = getattr(typecasts, k)(inpt)
+ assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/parts/django/tests/regressiontests/decorators/__init__.py b/parts/django/tests/regressiontests/decorators/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/decorators/__init__.py
diff --git a/parts/django/tests/regressiontests/decorators/models.py b/parts/django/tests/regressiontests/decorators/models.py
new file mode 100644
index 0000000..e5a7950
--- /dev/null
+++ b/parts/django/tests/regressiontests/decorators/models.py
@@ -0,0 +1,2 @@
+# A models.py so that tests run.
+
diff --git a/parts/django/tests/regressiontests/decorators/tests.py b/parts/django/tests/regressiontests/decorators/tests.py
new file mode 100644
index 0000000..ea2e10e
--- /dev/null
+++ b/parts/django/tests/regressiontests/decorators/tests.py
@@ -0,0 +1,141 @@
+from unittest import TestCase
+from sys import version_info
+try:
+ from functools import wraps
+except ImportError:
+ from django.utils.functional import wraps # Python 2.4 fallback.
+
+from django.http import HttpResponse, HttpRequest
+from django.utils.functional import allow_lazy, lazy, memoize
+from django.views.decorators.http import require_http_methods, require_GET, require_POST
+from django.views.decorators.vary import vary_on_headers, vary_on_cookie
+from django.views.decorators.cache import cache_page, never_cache, cache_control
+from django.utils.decorators import method_decorator
+from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
+from django.contrib.admin.views.decorators import staff_member_required
+
+def fully_decorated(request):
+ """Expected __doc__"""
+ return HttpResponse('<html><body>dummy</body></html>')
+fully_decorated.anything = "Expected __dict__"
+
+# django.views.decorators.http
+fully_decorated = require_http_methods(["GET"])(fully_decorated)
+fully_decorated = require_GET(fully_decorated)
+fully_decorated = require_POST(fully_decorated)
+
+# django.views.decorators.vary
+fully_decorated = vary_on_headers('Accept-language')(fully_decorated)
+fully_decorated = vary_on_cookie(fully_decorated)
+
+# django.views.decorators.cache
+fully_decorated = cache_page(60*15)(fully_decorated)
+fully_decorated = cache_control(private=True)(fully_decorated)
+fully_decorated = never_cache(fully_decorated)
+
+# django.contrib.auth.decorators
+# Apply user_passes_test twice to check #9474
+fully_decorated = user_passes_test(lambda u:True)(fully_decorated)
+fully_decorated = login_required(fully_decorated)
+fully_decorated = permission_required('change_world')(fully_decorated)
+
+# django.contrib.admin.views.decorators
+fully_decorated = staff_member_required(fully_decorated)
+
+# django.utils.functional
+fully_decorated = memoize(fully_decorated, {}, 1)
+fully_decorated = allow_lazy(fully_decorated)
+fully_decorated = lazy(fully_decorated)
+
+
+class DecoratorsTest(TestCase):
+
+ def test_attributes(self):
+ """
+ Tests that django decorators set certain attributes of the wrapped
+ function.
+ """
+ # Only check __name__ on Python 2.4 or later since __name__ can't be
+ # assigned to in earlier Python versions.
+ if version_info[0] >= 2 and version_info[1] >= 4:
+ self.assertEquals(fully_decorated.__name__, 'fully_decorated')
+ self.assertEquals(fully_decorated.__doc__, 'Expected __doc__')
+ self.assertEquals(fully_decorated.__dict__['anything'], 'Expected __dict__')
+
+ def test_user_passes_test_composition(self):
+ """
+ Test that the user_passes_test decorator can be applied multiple times
+ (#9474).
+ """
+ def test1(user):
+ user.decorators_applied.append('test1')
+ return True
+
+ def test2(user):
+ user.decorators_applied.append('test2')
+ return True
+
+ def callback(request):
+ return request.user.decorators_applied
+
+ callback = user_passes_test(test1)(callback)
+ callback = user_passes_test(test2)(callback)
+
+ class DummyUser(object): pass
+ class DummyRequest(object): pass
+
+ request = DummyRequest()
+ request.user = DummyUser()
+ request.user.decorators_applied = []
+ response = callback(request)
+
+ self.assertEqual(response, ['test2', 'test1'])
+
+ def test_cache_page_new_style(self):
+ """
+ Test that we can call cache_page the new way
+ """
+ def my_view(request):
+ return "response"
+ my_view_cached = cache_page(123)(my_view)
+ self.assertEqual(my_view_cached(HttpRequest()), "response")
+ my_view_cached2 = cache_page(123, key_prefix="test")(my_view)
+ self.assertEqual(my_view_cached2(HttpRequest()), "response")
+
+ def test_cache_page_old_style(self):
+ """
+ Test that we can call cache_page the old way
+ """
+ def my_view(request):
+ return "response"
+ my_view_cached = cache_page(my_view, 123)
+ self.assertEqual(my_view_cached(HttpRequest()), "response")
+ my_view_cached2 = cache_page(my_view, 123, key_prefix="test")
+ self.assertEqual(my_view_cached2(HttpRequest()), "response")
+ my_view_cached3 = cache_page(my_view)
+ self.assertEqual(my_view_cached3(HttpRequest()), "response")
+ my_view_cached4 = cache_page()(my_view)
+ self.assertEqual(my_view_cached4(HttpRequest()), "response")
+
+
+# For testing method_decorator, a decorator that assumes a single argument.
+# We will get type arguments if there is a mismatch in the number of arguments.
+def simple_dec(func):
+ def wrapper(arg):
+ return func("test:" + arg)
+ return wraps(func)(wrapper)
+
+simple_dec_m = method_decorator(simple_dec)
+
+
+class MethodDecoratorTests(TestCase):
+ """
+ Tests for method_decorator
+ """
+ def test_method_decorator(self):
+ class Test(object):
+ @simple_dec_m
+ def say(self, arg):
+ return arg
+
+ self.assertEqual("test:hello", Test().say("hello"))
diff --git a/parts/django/tests/regressiontests/defaultfilters/__init__.py b/parts/django/tests/regressiontests/defaultfilters/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/defaultfilters/__init__.py
diff --git a/parts/django/tests/regressiontests/defaultfilters/models.py b/parts/django/tests/regressiontests/defaultfilters/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/defaultfilters/models.py
diff --git a/parts/django/tests/regressiontests/defaultfilters/tests.py b/parts/django/tests/regressiontests/defaultfilters/tests.py
new file mode 100644
index 0000000..0866bfc
--- /dev/null
+++ b/parts/django/tests/regressiontests/defaultfilters/tests.py
@@ -0,0 +1,485 @@
+# -*- coding: utf-8 -*-
+import datetime
+import unittest
+
+from django.template.defaultfilters import *
+
+class DefaultFiltersTests(unittest.TestCase):
+
+ def test_floatformat(self):
+ self.assertEqual(floatformat(7.7), u'7.7')
+ self.assertEqual(floatformat(7.0), u'7')
+ self.assertEqual(floatformat(0.7), u'0.7')
+ self.assertEqual(floatformat(0.07), u'0.1')
+ self.assertEqual(floatformat(0.007), u'0.0')
+ self.assertEqual(floatformat(0.0), u'0')
+ self.assertEqual(floatformat(7.7, 3), u'7.700')
+ self.assertEqual(floatformat(6.000000, 3), u'6.000')
+ self.assertEqual(floatformat(6.200000, 3), u'6.200')
+ self.assertEqual(floatformat(6.200000, -3), u'6.200')
+ self.assertEqual(floatformat(13.1031, -3), u'13.103')
+ self.assertEqual(floatformat(11.1197, -2), u'11.12')
+ self.assertEqual(floatformat(11.0000, -2), u'11')
+ self.assertEqual(floatformat(11.000001, -2), u'11.00')
+ self.assertEqual(floatformat(8.2798, 3), u'8.280')
+ self.assertEqual(floatformat(u'foo'), u'')
+ self.assertEqual(floatformat(13.1031, u'bar'), u'13.1031')
+ self.assertEqual(floatformat(18.125, 2), u'18.13')
+ self.assertEqual(floatformat(u'foo', u'bar'), u'')
+ self.assertEqual(floatformat(u'¿Cómo esta usted?'), u'')
+ self.assertEqual(floatformat(None), u'')
+
+ pos_inf = float(1e30000)
+ self.assertEqual(floatformat(pos_inf), unicode(pos_inf))
+
+ neg_inf = float(-1e30000)
+ self.assertEqual(floatformat(neg_inf), unicode(neg_inf))
+
+ nan = pos_inf / pos_inf
+ self.assertEqual(floatformat(nan), unicode(nan))
+
+ class FloatWrapper(object):
+ def __init__(self, value):
+ self.value = value
+ def __float__(self):
+ return self.value
+
+ self.assertEqual(floatformat(FloatWrapper(11.000001), -2), u'11.00')
+
+ def test_addslashes(self):
+ self.assertEqual(addslashes(u'"double quotes" and \'single quotes\''),
+ u'\\"double quotes\\" and \\\'single quotes\\\'')
+
+ self.assertEqual(addslashes(ur'\ : backslashes, too'),
+ u'\\\\ : backslashes, too')
+
+ def test_capfirst(self):
+ self.assertEqual(capfirst(u'hello world'), u'Hello world')
+
+ def test_escapejs(self):
+ self.assertEqual(escapejs(u'"double quotes" and \'single quotes\''),
+ u'\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027')
+ self.assertEqual(escapejs(ur'\ : backslashes, too'),
+ u'\\u005C : backslashes, too')
+ self.assertEqual(escapejs(u'and lots of whitespace: \r\n\t\v\f\b'),
+ u'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008')
+ self.assertEqual(escapejs(ur'<script>and this</script>'),
+ u'\\u003Cscript\\u003Eand this\\u003C/script\\u003E')
+ self.assertEqual(
+ escapejs(u'paragraph separator:\u2029and line separator:\u2028'),
+ u'paragraph separator:\\u2029and line separator:\\u2028')
+
+ def test_fix_ampersands(self):
+ self.assertEqual(fix_ampersands(u'Jack & Jill & Jeroboam'),
+ u'Jack &amp; Jill &amp; Jeroboam')
+
+ def test_linenumbers(self):
+ self.assertEqual(linenumbers(u'line 1\nline 2'),
+ u'1. line 1\n2. line 2')
+ self.assertEqual(linenumbers(u'\n'.join([u'x'] * 10)),
+ u'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. '\
+ u'x\n08. x\n09. x\n10. x')
+
+ def test_lower(self):
+ self.assertEqual(lower('TEST'), u'test')
+
+ # uppercase E umlaut
+ self.assertEqual(lower(u'\xcb'), u'\xeb')
+
+ def test_make_list(self):
+ self.assertEqual(make_list('abc'), [u'a', u'b', u'c'])
+ self.assertEqual(make_list(1234), [u'1', u'2', u'3', u'4'])
+
+ def test_slugify(self):
+ self.assertEqual(slugify(' Jack & Jill like numbers 1,2,3 and 4 and'\
+ ' silly characters ?%.$!/'),
+ u'jack-jill-like-numbers-123-and-4-and-silly-characters')
+
+ self.assertEqual(slugify(u"Un \xe9l\xe9phant \xe0 l'or\xe9e du bois"),
+ u'un-elephant-a-loree-du-bois')
+
+ def test_stringformat(self):
+ self.assertEqual(stringformat(1, u'03d'), u'001')
+ self.assertEqual(stringformat(1, u'z'), u'')
+
+ def test_title(self):
+ self.assertEqual(title('a nice title, isn\'t it?'),
+ u"A Nice Title, Isn't It?")
+ self.assertEqual(title(u'discoth\xe8que'), u'Discoth\xe8que')
+
+ def test_truncatewords(self):
+ self.assertEqual(
+ truncatewords(u'A sentence with a few words in it', 1), u'A ...')
+ self.assertEqual(
+ truncatewords(u'A sentence with a few words in it', 5),
+ u'A sentence with a few ...')
+ self.assertEqual(
+ truncatewords(u'A sentence with a few words in it', 100),
+ u'A sentence with a few words in it')
+ self.assertEqual(
+ truncatewords(u'A sentence with a few words in it',
+ 'not a number'), u'A sentence with a few words in it')
+
+ def test_truncatewords_html(self):
+ self.assertEqual(truncatewords_html(
+ u'<p>one <a href="#">two - three <br>four</a> five</p>', 0), u'')
+ self.assertEqual(truncatewords_html(u'<p>one <a href="#">two - '\
+ u'three <br>four</a> five</p>', 2),
+ u'<p>one <a href="#">two ...</a></p>')
+ self.assertEqual(truncatewords_html(
+ u'<p>one <a href="#">two - three <br>four</a> five</p>', 4),
+ u'<p>one <a href="#">two - three <br>four ...</a></p>')
+ self.assertEqual(truncatewords_html(
+ u'<p>one <a href="#">two - three <br>four</a> five</p>', 5),
+ u'<p>one <a href="#">two - three <br>four</a> five</p>')
+ self.assertEqual(truncatewords_html(
+ u'<p>one <a href="#">two - three <br>four</a> five</p>', 100),
+ u'<p>one <a href="#">two - three <br>four</a> five</p>')
+ self.assertEqual(truncatewords_html(
+ u'\xc5ngstr\xf6m was here', 1), u'\xc5ngstr\xf6m ...')
+
+ def test_upper(self):
+ self.assertEqual(upper(u'Mixed case input'), u'MIXED CASE INPUT')
+ # lowercase e umlaut
+ self.assertEqual(upper(u'\xeb'), u'\xcb')
+
+ def test_urlencode(self):
+ self.assertEqual(urlencode(u'fran\xe7ois & jill'),
+ u'fran%C3%A7ois%20%26%20jill')
+ self.assertEqual(urlencode(1), u'1')
+
+ def test_iriencode(self):
+ self.assertEqual(iriencode(u'S\xf8r-Tr\xf8ndelag'),
+ u'S%C3%B8r-Tr%C3%B8ndelag')
+ self.assertEqual(iriencode(urlencode(u'fran\xe7ois & jill')),
+ u'fran%C3%A7ois%20%26%20jill')
+
+ def test_urlizetrunc(self):
+ self.assertEqual(urlizetrunc(u'http://short.com/', 20), u'<a href='\
+ u'"http://short.com/" rel="nofollow">http://short.com/</a>')
+
+ self.assertEqual(urlizetrunc(u'http://www.google.co.uk/search?hl=en'\
+ u'&q=some+long+url&btnG=Search&meta=', 20), u'<a href="http://'\
+ u'www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&'\
+ u'meta=" rel="nofollow">http://www.google...</a>')
+
+ self.assertEqual(urlizetrunc('http://www.google.co.uk/search?hl=en'\
+ u'&q=some+long+url&btnG=Search&meta=', 20), u'<a href="http://'\
+ u'www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search'\
+ u'&meta=" rel="nofollow">http://www.google...</a>')
+
+ # Check truncating of URIs which are the exact length
+ uri = 'http://31characteruri.com/test/'
+ self.assertEqual(len(uri), 31)
+
+ self.assertEqual(urlizetrunc(uri, 31),
+ u'<a href="http://31characteruri.com/test/" rel="nofollow">'\
+ u'http://31characteruri.com/test/</a>')
+
+ self.assertEqual(urlizetrunc(uri, 30),
+ u'<a href="http://31characteruri.com/test/" rel="nofollow">'\
+ u'http://31characteruri.com/t...</a>')
+
+ self.assertEqual(urlizetrunc(uri, 2),
+ u'<a href="http://31characteruri.com/test/"'\
+ u' rel="nofollow">...</a>')
+
+ def test_urlize(self):
+ # Check normal urlize
+ self.assertEqual(urlize('http://google.com'),
+ u'<a href="http://google.com" rel="nofollow">http://google.com</a>')
+ self.assertEqual(urlize('http://google.com/'),
+ u'<a href="http://google.com/" rel="nofollow">http://google.com/</a>')
+ self.assertEqual(urlize('www.google.com'),
+ u'<a href="http://www.google.com" rel="nofollow">www.google.com</a>')
+ self.assertEqual(urlize('djangoproject.org'),
+ u'<a href="http://djangoproject.org" rel="nofollow">djangoproject.org</a>')
+ self.assertEqual(urlize('info@djangoproject.org'),
+ u'<a href="mailto:info@djangoproject.org">info@djangoproject.org</a>')
+
+ # Check urlize with https addresses
+ self.assertEqual(urlize('https://google.com'),
+ u'<a href="https://google.com" rel="nofollow">https://google.com</a>')
+
+ def test_wordcount(self):
+ self.assertEqual(wordcount(''), 0)
+ self.assertEqual(wordcount(u'oneword'), 1)
+ self.assertEqual(wordcount(u'lots of words'), 3)
+
+ self.assertEqual(wordwrap(u'this is a long paragraph of text that '\
+ u'really needs to be wrapped I\'m afraid', 14),
+ u"this is a long\nparagraph of\ntext that\nreally needs\nto be "\
+ u"wrapped\nI'm afraid")
+
+ self.assertEqual(wordwrap(u'this is a short paragraph of text.\n '\
+ u'But this line should be indented', 14),
+ u'this is a\nshort\nparagraph of\ntext.\n But this\nline '\
+ u'should be\nindented')
+
+ self.assertEqual(wordwrap(u'this is a short paragraph of text.\n '\
+ u'But this line should be indented',15), u'this is a short\n'\
+ u'paragraph of\ntext.\n But this line\nshould be\nindented')
+
+ def test_rjust(self):
+ self.assertEqual(ljust(u'test', 10), u'test ')
+ self.assertEqual(ljust(u'test', 3), u'test')
+ self.assertEqual(rjust(u'test', 10), u' test')
+ self.assertEqual(rjust(u'test', 3), u'test')
+
+ def test_center(self):
+ self.assertEqual(center(u'test', 6), u' test ')
+
+ def test_cut(self):
+ self.assertEqual(cut(u'a string to be mangled', 'a'),
+ u' string to be mngled')
+ self.assertEqual(cut(u'a string to be mangled', 'ng'),
+ u'a stri to be maled')
+ self.assertEqual(cut(u'a string to be mangled', 'strings'),
+ u'a string to be mangled')
+
+ def test_force_escape(self):
+ self.assertEqual(
+ force_escape(u'<some html & special characters > here'),
+ u'&lt;some html &amp; special characters &gt; here')
+ self.assertEqual(
+ force_escape(u'<some html & special characters > here ĐÅ€£'),
+ u'&lt;some html &amp; special characters &gt; here'\
+ u' \u0110\xc5\u20ac\xa3')
+
+ def test_linebreaks(self):
+ self.assertEqual(linebreaks(u'line 1'), u'<p>line 1</p>')
+ self.assertEqual(linebreaks(u'line 1\nline 2'),
+ u'<p>line 1<br />line 2</p>')
+
+ def test_removetags(self):
+ self.assertEqual(removetags(u'some <b>html</b> with <script>alert'\
+ u'("You smell")</script> disallowed <img /> tags', 'script img'),
+ u'some <b>html</b> with alert("You smell") disallowed tags')
+ self.assertEqual(striptags(u'some <b>html</b> with <script>alert'\
+ u'("You smell")</script> disallowed <img /> tags'),
+ u'some html with alert("You smell") disallowed tags')
+
+ def test_dictsort(self):
+ sorted_dicts = dictsort([{'age': 23, 'name': 'Barbara-Ann'},
+ {'age': 63, 'name': 'Ra Ra Rasputin'},
+ {'name': 'Jonny B Goode', 'age': 18}], 'age')
+
+ self.assertEqual([sorted(dict.items()) for dict in sorted_dicts],
+ [[('age', 18), ('name', 'Jonny B Goode')],
+ [('age', 23), ('name', 'Barbara-Ann')],
+ [('age', 63), ('name', 'Ra Ra Rasputin')]])
+
+ def test_dictsortreversed(self):
+ sorted_dicts = dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},
+ {'age': 63, 'name': 'Ra Ra Rasputin'},
+ {'name': 'Jonny B Goode', 'age': 18}],
+ 'age')
+
+ self.assertEqual([sorted(dict.items()) for dict in sorted_dicts],
+ [[('age', 63), ('name', 'Ra Ra Rasputin')],
+ [('age', 23), ('name', 'Barbara-Ann')],
+ [('age', 18), ('name', 'Jonny B Goode')]])
+
+ def test_first(self):
+ self.assertEqual(first([0,1,2]), 0)
+ self.assertEqual(first(u''), u'')
+ self.assertEqual(first(u'test'), u't')
+
+ def test_join(self):
+ self.assertEqual(join([0,1,2], u'glue'), u'0glue1glue2')
+
+ def test_length(self):
+ self.assertEqual(length(u'1234'), 4)
+ self.assertEqual(length([1,2,3,4]), 4)
+ self.assertEqual(length_is([], 0), True)
+ self.assertEqual(length_is([], 1), False)
+ self.assertEqual(length_is('a', 1), True)
+ self.assertEqual(length_is(u'a', 10), False)
+
+ def test_slice(self):
+ self.assertEqual(slice_(u'abcdefg', u'0'), u'')
+ self.assertEqual(slice_(u'abcdefg', u'1'), u'a')
+ self.assertEqual(slice_(u'abcdefg', u'-1'), u'abcdef')
+ self.assertEqual(slice_(u'abcdefg', u'1:2'), u'b')
+ self.assertEqual(slice_(u'abcdefg', u'1:3'), u'bc')
+ self.assertEqual(slice_(u'abcdefg', u'0::2'), u'aceg')
+
+ def test_unordered_list(self):
+ self.assertEqual(unordered_list([u'item 1', u'item 2']),
+ u'\t<li>item 1</li>\n\t<li>item 2</li>')
+ self.assertEqual(unordered_list([u'item 1', [u'item 1.1']]),
+ u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>')
+
+ self.assertEqual(
+ unordered_list([u'item 1', [u'item 1.1', u'item1.2'], u'item 2']),
+ u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item1.2'\
+ u'</li>\n\t</ul>\n\t</li>\n\t<li>item 2</li>')
+
+ self.assertEqual(
+ unordered_list([u'item 1', [u'item 1.1', [u'item 1.1.1',
+ [u'item 1.1.1.1']]]]),
+ u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1\n\t\t<ul>\n\t\t\t<li>'\
+ u'item 1.1.1\n\t\t\t<ul>\n\t\t\t\t<li>item 1.1.1.1</li>\n\t\t\t'\
+ u'</ul>\n\t\t\t</li>\n\t\t</ul>\n\t\t</li>\n\t</ul>\n\t</li>')
+
+ self.assertEqual(unordered_list(
+ ['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]),
+ u'\t<li>States\n\t<ul>\n\t\t<li>Kansas\n\t\t<ul>\n\t\t\t<li>'\
+ u'Lawrence</li>\n\t\t\t<li>Topeka</li>\n\t\t</ul>\n\t\t</li>'\
+ u'\n\t\t<li>Illinois</li>\n\t</ul>\n\t</li>')
+
+ class ULItem(object):
+ def __init__(self, title):
+ self.title = title
+ def __unicode__(self):
+ return u'ulitem-%s' % str(self.title)
+
+ a = ULItem('a')
+ b = ULItem('b')
+ self.assertEqual(unordered_list([a,b]),
+ u'\t<li>ulitem-a</li>\n\t<li>ulitem-b</li>')
+
+ # Old format for unordered lists should still work
+ self.assertEqual(unordered_list([u'item 1', []]), u'\t<li>item 1</li>')
+
+ self.assertEqual(unordered_list([u'item 1', [[u'item 1.1', []]]]),
+ u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>')
+
+ self.assertEqual(unordered_list([u'item 1', [[u'item 1.1', []],
+ [u'item 1.2', []]]]), u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1'\
+ u'</li>\n\t\t<li>item 1.2</li>\n\t</ul>\n\t</li>')
+
+ self.assertEqual(unordered_list(['States', [['Kansas', [['Lawrence',
+ []], ['Topeka', []]]], ['Illinois', []]]]), u'\t<li>States\n\t'\
+ u'<ul>\n\t\t<li>Kansas\n\t\t<ul>\n\t\t\t<li>Lawrence</li>'\
+ u'\n\t\t\t<li>Topeka</li>\n\t\t</ul>\n\t\t</li>\n\t\t<li>'\
+ u'Illinois</li>\n\t</ul>\n\t</li>')
+
+ def test_add(self):
+ self.assertEqual(add(u'1', u'2'), 3)
+
+ def test_get_digit(self):
+ self.assertEqual(get_digit(123, 1), 3)
+ self.assertEqual(get_digit(123, 2), 2)
+ self.assertEqual(get_digit(123, 3), 1)
+ self.assertEqual(get_digit(123, 4), 0)
+ self.assertEqual(get_digit(123, 0), 123)
+ self.assertEqual(get_digit(u'xyz', 0), u'xyz')
+
+ def test_date(self):
+ # real testing of date() is in dateformat.py
+ self.assertEqual(date(datetime.datetime(2005, 12, 29), u"d F Y"),
+ u'29 December 2005')
+ self.assertEqual(date(datetime.datetime(2005, 12, 29), ur'jS o\f F'),
+ u'29th of December')
+
+ def test_time(self):
+ # real testing of time() is done in dateformat.py
+ self.assertEqual(time(datetime.time(13), u"h"), u'01')
+ self.assertEqual(time(datetime.time(0), u"h"), u'12')
+
+ def test_timesince(self):
+ # real testing is done in timesince.py, where we can provide our own 'now'
+ self.assertEqual(
+ timesince(datetime.datetime.now() - datetime.timedelta(1)),
+ u'1 day')
+
+ self.assertEqual(
+ timesince(datetime.datetime(2005, 12, 29),
+ datetime.datetime(2005, 12, 30)),
+ u'1 day')
+
+ def test_timeuntil(self):
+ self.assertEqual(
+ timeuntil(datetime.datetime.now() + datetime.timedelta(1)),
+ u'1 day')
+
+ self.assertEqual(timeuntil(datetime.datetime(2005, 12, 30),
+ datetime.datetime(2005, 12, 29)),
+ u'1 day')
+
+ def test_default(self):
+ self.assertEqual(default(u"val", u"default"), u'val')
+ self.assertEqual(default(None, u"default"), u'default')
+ self.assertEqual(default(u'', u"default"), u'default')
+
+ def test_if_none(self):
+ self.assertEqual(default_if_none(u"val", u"default"), u'val')
+ self.assertEqual(default_if_none(None, u"default"), u'default')
+ self.assertEqual(default_if_none(u'', u"default"), u'')
+
+ def test_divisibleby(self):
+ self.assertEqual(divisibleby(4, 2), True)
+ self.assertEqual(divisibleby(4, 3), False)
+
+ def test_yesno(self):
+ self.assertEqual(yesno(True), u'yes')
+ self.assertEqual(yesno(False), u'no')
+ self.assertEqual(yesno(None), u'maybe')
+ self.assertEqual(yesno(True, u'certainly,get out of town,perhaps'),
+ u'certainly')
+ self.assertEqual(yesno(False, u'certainly,get out of town,perhaps'),
+ u'get out of town')
+ self.assertEqual(yesno(None, u'certainly,get out of town,perhaps'),
+ u'perhaps')
+ self.assertEqual(yesno(None, u'certainly,get out of town'),
+ u'get out of town')
+
+ def test_filesizeformat(self):
+ self.assertEqual(filesizeformat(1023), u'1023 bytes')
+ self.assertEqual(filesizeformat(1024), u'1.0 KB')
+ self.assertEqual(filesizeformat(10*1024), u'10.0 KB')
+ self.assertEqual(filesizeformat(1024*1024-1), u'1024.0 KB')
+ self.assertEqual(filesizeformat(1024*1024), u'1.0 MB')
+ self.assertEqual(filesizeformat(1024*1024*50), u'50.0 MB')
+ self.assertEqual(filesizeformat(1024*1024*1024-1), u'1024.0 MB')
+ self.assertEqual(filesizeformat(1024*1024*1024), u'1.0 GB')
+ self.assertEqual(filesizeformat(complex(1,-1)), u'0 bytes')
+ self.assertEqual(filesizeformat(""), u'0 bytes')
+ self.assertEqual(filesizeformat(u"\N{GREEK SMALL LETTER ALPHA}"),
+ u'0 bytes')
+
+ def test_pluralize(self):
+ self.assertEqual(pluralize(1), u'')
+ self.assertEqual(pluralize(0), u's')
+ self.assertEqual(pluralize(2), u's')
+ self.assertEqual(pluralize([1]), u'')
+ self.assertEqual(pluralize([]), u's')
+ self.assertEqual(pluralize([1,2,3]), u's')
+ self.assertEqual(pluralize(1,u'es'), u'')
+ self.assertEqual(pluralize(0,u'es'), u'es')
+ self.assertEqual(pluralize(2,u'es'), u'es')
+ self.assertEqual(pluralize(1,u'y,ies'), u'y')
+ self.assertEqual(pluralize(0,u'y,ies'), u'ies')
+ self.assertEqual(pluralize(2,u'y,ies'), u'ies')
+ self.assertEqual(pluralize(0,u'y,ies,error'), u'')
+
+ def test_phone2numeric(self):
+ self.assertEqual(phone2numeric(u'0800 flowers'), u'0800 3569377')
+
+ def test_non_string_input(self):
+ # Filters shouldn't break if passed non-strings
+ self.assertEqual(addslashes(123), u'123')
+ self.assertEqual(linenumbers(123), u'1. 123')
+ self.assertEqual(lower(123), u'123')
+ self.assertEqual(make_list(123), [u'1', u'2', u'3'])
+ self.assertEqual(slugify(123), u'123')
+ self.assertEqual(title(123), u'123')
+ self.assertEqual(truncatewords(123, 2), u'123')
+ self.assertEqual(upper(123), u'123')
+ self.assertEqual(urlencode(123), u'123')
+ self.assertEqual(urlize(123), u'123')
+ self.assertEqual(urlizetrunc(123, 1), u'123')
+ self.assertEqual(wordcount(123), 1)
+ self.assertEqual(wordwrap(123, 2), u'123')
+ self.assertEqual(ljust('123', 4), u'123 ')
+ self.assertEqual(rjust('123', 4), u' 123')
+ self.assertEqual(center('123', 5), u' 123 ')
+ self.assertEqual(center('123', 6), u' 123 ')
+ self.assertEqual(cut(123, '2'), u'13')
+ self.assertEqual(escape(123), u'123')
+ self.assertEqual(linebreaks(123), u'<p>123</p>')
+ self.assertEqual(linebreaksbr(123), u'123')
+ self.assertEqual(removetags(123, 'a'), u'123')
+ self.assertEqual(striptags(123), u'123')
+
diff --git a/parts/django/tests/regressiontests/defer_regress/__init__.py b/parts/django/tests/regressiontests/defer_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/defer_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/defer_regress/models.py b/parts/django/tests/regressiontests/defer_regress/models.py
new file mode 100644
index 0000000..8db8c29
--- /dev/null
+++ b/parts/django/tests/regressiontests/defer_regress/models.py
@@ -0,0 +1,36 @@
+"""
+Regression tests for defer() / only() behavior.
+"""
+
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.db import connection, models
+
+class Item(models.Model):
+ name = models.CharField(max_length=15)
+ text = models.TextField(default="xyzzy")
+ value = models.IntegerField()
+ other_value = models.IntegerField(default=0)
+
+ def __unicode__(self):
+ return self.name
+
+class RelatedItem(models.Model):
+ item = models.ForeignKey(Item)
+
+class Child(models.Model):
+ name = models.CharField(max_length=10)
+ value = models.IntegerField()
+
+class Leaf(models.Model):
+ name = models.CharField(max_length=10)
+ child = models.ForeignKey(Child)
+ second_child = models.ForeignKey(Child, related_name="other", null=True)
+ value = models.IntegerField(default=42)
+
+ def __unicode__(self):
+ return self.name
+
+class ResolveThis(models.Model):
+ num = models.FloatField()
+ name = models.CharField(max_length=16)
diff --git a/parts/django/tests/regressiontests/defer_regress/tests.py b/parts/django/tests/regressiontests/defer_regress/tests.py
new file mode 100644
index 0000000..05f1279
--- /dev/null
+++ b/parts/django/tests/regressiontests/defer_regress/tests.py
@@ -0,0 +1,163 @@
+from operator import attrgetter
+
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sessions.backends.db import SessionStore
+from django.db import connection
+from django.db.models.loading import cache
+from django.test import TestCase
+
+from models import ResolveThis, Item, RelatedItem, Child, Leaf
+
+
+class DeferRegressionTest(TestCase):
+ def assert_num_queries(self, n, func, *args, **kwargs):
+ old_DEBUG = settings.DEBUG
+ settings.DEBUG = True
+ starting_queries = len(connection.queries)
+ try:
+ func(*args, **kwargs)
+ finally:
+ settings.DEBUG = old_DEBUG
+ self.assertEqual(starting_queries + n, len(connection.queries))
+
+ def test_basic(self):
+ # Deferred fields should really be deferred and not accidentally use
+ # the field's default value just because they aren't passed to __init__
+
+ Item.objects.create(name="first", value=42)
+ obj = Item.objects.only("name", "other_value").get(name="first")
+ # Accessing "name" doesn't trigger a new database query. Accessing
+ # "value" or "text" should.
+ def test():
+ self.assertEqual(obj.name, "first")
+ self.assertEqual(obj.other_value, 0)
+ self.assert_num_queries(0, test)
+
+ def test():
+ self.assertEqual(obj.value, 42)
+ self.assert_num_queries(1, test)
+
+ def test():
+ self.assertEqual(obj.text, "xyzzy")
+ self.assert_num_queries(1, test)
+
+ def test():
+ self.assertEqual(obj.text, "xyzzy")
+ self.assert_num_queries(0, test)
+
+ # Regression test for #10695. Make sure different instances don't
+ # inadvertently share data in the deferred descriptor objects.
+ i = Item.objects.create(name="no I'm first", value=37)
+ items = Item.objects.only("value").order_by("-value")
+ self.assertEqual(items[0].name, "first")
+ self.assertEqual(items[1].name, "no I'm first")
+
+ RelatedItem.objects.create(item=i)
+ r = RelatedItem.objects.defer("item").get()
+ self.assertEqual(r.item_id, i.id)
+ self.assertEqual(r.item, i)
+
+ # Some further checks for select_related() and inherited model
+ # behaviour (regression for #10710).
+ c1 = Child.objects.create(name="c1", value=42)
+ c2 = Child.objects.create(name="c2", value=37)
+ Leaf.objects.create(name="l1", child=c1, second_child=c2)
+
+ obj = Leaf.objects.only("name", "child").select_related()[0]
+ self.assertEqual(obj.child.name, "c1")
+
+ self.assertQuerysetEqual(
+ Leaf.objects.select_related().only("child__name", "second_child__name"), [
+ "l1",
+ ],
+ attrgetter("name")
+ )
+
+ # Models instances with deferred fields should still return the same
+ # content types as their non-deferred versions (bug #10738).
+ ctype = ContentType.objects.get_for_model
+ c1 = ctype(Item.objects.all()[0])
+ c2 = ctype(Item.objects.defer("name")[0])
+ c3 = ctype(Item.objects.only("name")[0])
+ self.assertTrue(c1 is c2 is c3)
+
+ # Regression for #10733 - only() can be used on a model with two
+ # foreign keys.
+ results = Leaf.objects.only("name", "child", "second_child").select_related()
+ self.assertEqual(results[0].child.name, "c1")
+ self.assertEqual(results[0].second_child.name, "c2")
+
+ results = Leaf.objects.only("name", "child", "second_child", "child__name", "second_child__name").select_related()
+ self.assertEqual(results[0].child.name, "c1")
+ self.assertEqual(results[0].second_child.name, "c2")
+
+ # Test for #12163 - Pickling error saving session with unsaved model
+ # instances.
+ SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead'
+
+ item = Item()
+ item._deferred = False
+ s = SessionStore(SESSION_KEY)
+ s.clear()
+ s["item"] = item
+ s.save()
+
+ s = SessionStore(SESSION_KEY)
+ s.modified = True
+ s.save()
+
+ i2 = s["item"]
+ self.assertFalse(i2._deferred)
+
+ # Regression for #11936 - loading.get_models should not return deferred
+ # models by default.
+ klasses = sorted(
+ cache.get_models(cache.get_app("defer_regress")),
+ key=lambda klass: klass.__name__
+ )
+ self.assertEqual(
+ klasses, [
+ Child,
+ Item,
+ Leaf,
+ RelatedItem,
+ ResolveThis,
+ ]
+ )
+
+ klasses = sorted(
+ map(
+ attrgetter("__name__"),
+ cache.get_models(
+ cache.get_app("defer_regress"), include_deferred=True
+ ),
+ )
+ )
+ self.assertEqual(
+ klasses, [
+ "Child",
+ "Child_Deferred_value",
+ "Item",
+ "Item_Deferred_name",
+ "Item_Deferred_name_other_value_text",
+ "Item_Deferred_name_other_value_value",
+ "Item_Deferred_other_value_text_value",
+ "Item_Deferred_text_value",
+ "Leaf",
+ "Leaf_Deferred_child_id_second_child_id_value",
+ "Leaf_Deferred_name_value",
+ "Leaf_Deferred_second_child_value",
+ "Leaf_Deferred_value",
+ "RelatedItem",
+ "RelatedItem_Deferred_",
+ "RelatedItem_Deferred_item_id",
+ "ResolveThis",
+ ]
+ )
+
+ def test_resolve_columns(self):
+ rt = ResolveThis.objects.create(num=5.0, name='Foobar')
+ qs = ResolveThis.objects.defer('num')
+ self.assertEqual(1, qs.count())
+ self.assertEqual('Foobar', qs[0].name)
diff --git a/parts/django/tests/regressiontests/delete_regress/__init__.py b/parts/django/tests/regressiontests/delete_regress/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/delete_regress/__init__.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/delete_regress/models.py b/parts/django/tests/regressiontests/delete_regress/models.py
new file mode 100644
index 0000000..8109c0a
--- /dev/null
+++ b/parts/django/tests/regressiontests/delete_regress/models.py
@@ -0,0 +1,37 @@
+from django.db import models
+
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+
+class Award(models.Model):
+ name = models.CharField(max_length=25)
+ object_id = models.PositiveIntegerField()
+ content_type = models.ForeignKey(ContentType)
+ content_object = generic.GenericForeignKey()
+
+class AwardNote(models.Model):
+ award = models.ForeignKey(Award)
+ note = models.CharField(max_length=100)
+
+class Person(models.Model):
+ name = models.CharField(max_length=25)
+ awards = generic.GenericRelation(Award)
+
+class Book(models.Model):
+ pagecount = models.IntegerField()
+
+class Toy(models.Model):
+ name = models.CharField(max_length=50)
+
+class Child(models.Model):
+ name = models.CharField(max_length=50)
+ toys = models.ManyToManyField(Toy, through='PlayedWith')
+
+class PlayedWith(models.Model):
+ child = models.ForeignKey(Child)
+ toy = models.ForeignKey(Toy)
+ date = models.DateField(db_column='date_col')
+
+class PlayedWithNote(models.Model):
+ played = models.ForeignKey(PlayedWith)
+ note = models.TextField()
diff --git a/parts/django/tests/regressiontests/delete_regress/tests.py b/parts/django/tests/regressiontests/delete_regress/tests.py
new file mode 100644
index 0000000..26cd3c5
--- /dev/null
+++ b/parts/django/tests/regressiontests/delete_regress/tests.py
@@ -0,0 +1,116 @@
+import datetime
+
+from django.conf import settings
+from django.db import backend, connection, transaction, DEFAULT_DB_ALIAS
+from django.test import TestCase, TransactionTestCase
+
+from models import Book, Award, AwardNote, Person, Child, Toy, PlayedWith, PlayedWithNote
+
+# Can't run this test under SQLite, because you can't
+# get two connections to an in-memory database.
+if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3':
+ class DeleteLockingTest(TransactionTestCase):
+ def setUp(self):
+ # Create a second connection to the default database
+ conn_settings = settings.DATABASES[DEFAULT_DB_ALIAS]
+ self.conn2 = backend.DatabaseWrapper({
+ 'HOST': conn_settings['HOST'],
+ 'NAME': conn_settings['NAME'],
+ 'OPTIONS': conn_settings['OPTIONS'],
+ 'PASSWORD': conn_settings['PASSWORD'],
+ 'PORT': conn_settings['PORT'],
+ 'USER': conn_settings['USER'],
+ 'TIME_ZONE': settings.TIME_ZONE,
+ })
+
+ # Put both DB connections into managed transaction mode
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+ self.conn2._enter_transaction_management(True)
+
+ def tearDown(self):
+ # Close down the second connection.
+ transaction.leave_transaction_management()
+ self.conn2.close()
+
+ def test_concurrent_delete(self):
+ "Deletes on concurrent transactions don't collide and lock the database. Regression for #9479"
+
+ # Create some dummy data
+ b1 = Book(id=1, pagecount=100)
+ b2 = Book(id=2, pagecount=200)
+ b3 = Book(id=3, pagecount=300)
+ b1.save()
+ b2.save()
+ b3.save()
+
+ transaction.commit()
+
+ self.assertEquals(3, Book.objects.count())
+
+ # Delete something using connection 2.
+ cursor2 = self.conn2.cursor()
+ cursor2.execute('DELETE from delete_regress_book WHERE id=1')
+ self.conn2._commit();
+
+ # Now perform a queryset delete that covers the object
+ # deleted in connection 2. This causes an infinite loop
+ # under MySQL InnoDB unless we keep track of already
+ # deleted objects.
+ Book.objects.filter(pagecount__lt=250).delete()
+ transaction.commit()
+ self.assertEquals(1, Book.objects.count())
+
+class DeleteCascadeTests(TestCase):
+ def test_generic_relation_cascade(self):
+ """
+ Test that Django cascades deletes through generic-related
+ objects to their reverse relations.
+
+ This might falsely succeed if the database cascades deletes
+ itself immediately; the postgresql_psycopg2 backend does not
+ give such a false success because ForeignKeys are created with
+ DEFERRABLE INITIALLY DEFERRED, so its internal cascade is
+ delayed until transaction commit.
+
+ """
+ person = Person.objects.create(name='Nelson Mandela')
+ award = Award.objects.create(name='Nobel', content_object=person)
+ note = AwardNote.objects.create(note='a peace prize',
+ award=award)
+ self.assertEquals(AwardNote.objects.count(), 1)
+ person.delete()
+ self.assertEquals(Award.objects.count(), 0)
+ # first two asserts are just sanity checks, this is the kicker:
+ self.assertEquals(AwardNote.objects.count(), 0)
+
+ def test_fk_to_m2m_through(self):
+ """
+ Test that if a M2M relationship has an explicitly-specified
+ through model, and some other model has an FK to that through
+ model, deletion is cascaded from one of the participants in
+ the M2M, to the through model, to its related model.
+
+ Like the above test, this could in theory falsely succeed if
+ the DB cascades deletes itself immediately.
+
+ """
+ juan = Child.objects.create(name='Juan')
+ paints = Toy.objects.create(name='Paints')
+ played = PlayedWith.objects.create(child=juan, toy=paints,
+ date=datetime.date.today())
+ note = PlayedWithNote.objects.create(played=played,
+ note='the next Jackson Pollock')
+ self.assertEquals(PlayedWithNote.objects.count(), 1)
+ paints.delete()
+ self.assertEquals(PlayedWith.objects.count(), 0)
+ # first two asserts just sanity checks, this is the kicker:
+ self.assertEquals(PlayedWithNote.objects.count(), 0)
+
+class LargeDeleteTests(TestCase):
+ def test_large_deletes(self):
+ "Regression for #13309 -- if the number of objects > chunk size, deletion still occurs"
+ for x in range(300):
+ track = Book.objects.create(pagecount=x+100)
+ Book.objects.all().delete()
+ self.assertEquals(Book.objects.count(), 0)
diff --git a/parts/django/tests/regressiontests/dispatch/__init__.py b/parts/django/tests/regressiontests/dispatch/__init__.py
new file mode 100644
index 0000000..679895b
--- /dev/null
+++ b/parts/django/tests/regressiontests/dispatch/__init__.py
@@ -0,0 +1,2 @@
+"""Unit-tests for the dispatch project
+"""
diff --git a/parts/django/tests/regressiontests/dispatch/models.py b/parts/django/tests/regressiontests/dispatch/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/dispatch/models.py
diff --git a/parts/django/tests/regressiontests/dispatch/tests/__init__.py b/parts/django/tests/regressiontests/dispatch/tests/__init__.py
new file mode 100644
index 0000000..150bb01
--- /dev/null
+++ b/parts/django/tests/regressiontests/dispatch/tests/__init__.py
@@ -0,0 +1,6 @@
+"""
+Unit-tests for the dispatch project
+"""
+
+from test_saferef import *
+from test_dispatcher import *
diff --git a/parts/django/tests/regressiontests/dispatch/tests/test_dispatcher.py b/parts/django/tests/regressiontests/dispatch/tests/test_dispatcher.py
new file mode 100644
index 0000000..ad3a05f
--- /dev/null
+++ b/parts/django/tests/regressiontests/dispatch/tests/test_dispatcher.py
@@ -0,0 +1,123 @@
+from django.dispatch import Signal
+import unittest
+import sys
+import gc
+import django.utils.copycompat as copy
+
+if sys.platform.startswith('java'):
+ def garbage_collect():
+ """Run the garbage collector and wait a bit to let it do his work"""
+ import time
+ gc.collect()
+ time.sleep(0.1)
+else:
+ def garbage_collect():
+ gc.collect()
+
+def receiver_1_arg(val, **kwargs):
+ return val
+
+class Callable(object):
+ def __call__(self, val, **kwargs):
+ return val
+
+ def a(self, val, **kwargs):
+ return val
+
+a_signal = Signal(providing_args=["val"])
+
+class DispatcherTests(unittest.TestCase):
+ """Test suite for dispatcher (barely started)"""
+
+ def _testIsClean(self, signal):
+ """Assert that everything has been cleaned up automatically"""
+ self.assertEqual(signal.receivers, [])
+
+ # force cleanup just in case
+ signal.receivers = []
+
+ def testExact(self):
+ a_signal.connect(receiver_1_arg, sender=self)
+ expected = [(receiver_1_arg,"test")]
+ result = a_signal.send(sender=self, val="test")
+ self.assertEqual(result, expected)
+ a_signal.disconnect(receiver_1_arg, sender=self)
+ self._testIsClean(a_signal)
+
+ def testIgnoredSender(self):
+ a_signal.connect(receiver_1_arg)
+ expected = [(receiver_1_arg,"test")]
+ result = a_signal.send(sender=self, val="test")
+ self.assertEqual(result, expected)
+ a_signal.disconnect(receiver_1_arg)
+ self._testIsClean(a_signal)
+
+ def testGarbageCollected(self):
+ a = Callable()
+ a_signal.connect(a.a, sender=self)
+ expected = []
+ del a
+ garbage_collect()
+ result = a_signal.send(sender=self, val="test")
+ self.assertEqual(result, expected)
+ self._testIsClean(a_signal)
+
+ def testMultipleRegistration(self):
+ a = Callable()
+ a_signal.connect(a)
+ a_signal.connect(a)
+ a_signal.connect(a)
+ a_signal.connect(a)
+ a_signal.connect(a)
+ a_signal.connect(a)
+ result = a_signal.send(sender=self, val="test")
+ self.assertEqual(len(result), 1)
+ self.assertEqual(len(a_signal.receivers), 1)
+ del a
+ del result
+ garbage_collect()
+ self._testIsClean(a_signal)
+
+ def testUidRegistration(self):
+ def uid_based_receiver_1(**kwargs):
+ pass
+
+ def uid_based_receiver_2(**kwargs):
+ pass
+
+ a_signal.connect(uid_based_receiver_1, dispatch_uid = "uid")
+ a_signal.connect(uid_based_receiver_2, dispatch_uid = "uid")
+ self.assertEqual(len(a_signal.receivers), 1)
+ a_signal.disconnect(dispatch_uid = "uid")
+ self._testIsClean(a_signal)
+
+ def testRobust(self):
+ """Test the sendRobust function"""
+ def fails(val, **kwargs):
+ raise ValueError('this')
+ a_signal.connect(fails)
+ result = a_signal.send_robust(sender=self, val="test")
+ err = result[0][1]
+ self.assert_(isinstance(err, ValueError))
+ self.assertEqual(err.args, ('this',))
+ a_signal.disconnect(fails)
+ self._testIsClean(a_signal)
+
+ def testDisconnection(self):
+ receiver_1 = Callable()
+ receiver_2 = Callable()
+ receiver_3 = Callable()
+ a_signal.connect(receiver_1)
+ a_signal.connect(receiver_2)
+ a_signal.connect(receiver_3)
+ a_signal.disconnect(receiver_1)
+ del receiver_2
+ garbage_collect()
+ a_signal.disconnect(receiver_3)
+ self._testIsClean(a_signal)
+
+def getSuite():
+ return unittest.makeSuite(DispatcherTests,'test')
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/parts/django/tests/regressiontests/dispatch/tests/test_saferef.py b/parts/django/tests/regressiontests/dispatch/tests/test_saferef.py
new file mode 100644
index 0000000..c0ec879
--- /dev/null
+++ b/parts/django/tests/regressiontests/dispatch/tests/test_saferef.py
@@ -0,0 +1,79 @@
+from django.dispatch.saferef import *
+
+import unittest
+
+class Test1(object):
+ def x(self):
+ pass
+
+def test2(obj):
+ pass
+
+class Test2(object):
+ def __call__(self, obj):
+ pass
+
+class Tester(unittest.TestCase):
+ def setUp(self):
+ ts = []
+ ss = []
+ for x in xrange(5000):
+ t = Test1()
+ ts.append(t)
+ s = safeRef(t.x, self._closure)
+ ss.append(s)
+ ts.append(test2)
+ ss.append(safeRef(test2, self._closure))
+ for x in xrange(30):
+ t = Test2()
+ ts.append(t)
+ s = safeRef(t, self._closure)
+ ss.append(s)
+ self.ts = ts
+ self.ss = ss
+ self.closureCount = 0
+
+ def tearDown(self):
+ del self.ts
+ del self.ss
+
+ def testIn(self):
+ """Test the "in" operator for safe references (cmp)"""
+ for t in self.ts[:50]:
+ self.assert_(safeRef(t.x) in self.ss)
+
+ def testValid(self):
+ """Test that the references are valid (return instance methods)"""
+ for s in self.ss:
+ self.assert_(s())
+
+ def testShortCircuit (self):
+ """Test that creation short-circuits to reuse existing references"""
+ sd = {}
+ for s in self.ss:
+ sd[s] = 1
+ for t in self.ts:
+ if hasattr(t, 'x'):
+ self.assert_(sd.has_key(safeRef(t.x)))
+ self.assert_(safeRef(t.x) in sd)
+ else:
+ self.assert_(sd.has_key(safeRef(t)))
+ self.assert_(safeRef(t) in sd)
+
+ def testRepresentation (self):
+ """Test that the reference object's representation works
+
+ XXX Doesn't currently check the results, just that no error
+ is raised
+ """
+ repr(self.ss[-1])
+
+ def _closure(self, ref):
+ """Dumb utility mechanism to increment deletion counter"""
+ self.closureCount +=1
+
+def getSuite():
+ return unittest.makeSuite(Tester,'test')
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/parts/django/tests/regressiontests/expressions_regress/__init__.py b/parts/django/tests/regressiontests/expressions_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/expressions_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/expressions_regress/models.py b/parts/django/tests/regressiontests/expressions_regress/models.py
new file mode 100644
index 0000000..f2997ee
--- /dev/null
+++ b/parts/django/tests/regressiontests/expressions_regress/models.py
@@ -0,0 +1,12 @@
+"""
+Model for testing arithmetic expressions.
+"""
+from django.db import models
+
+class Number(models.Model):
+ integer = models.IntegerField(db_column='the_integer')
+ float = models.FloatField(null=True, db_column='the_float')
+
+ def __unicode__(self):
+ return u'%i, %.3f' % (self.integer, self.float)
+
diff --git a/parts/django/tests/regressiontests/expressions_regress/tests.py b/parts/django/tests/regressiontests/expressions_regress/tests.py
new file mode 100644
index 0000000..a662728
--- /dev/null
+++ b/parts/django/tests/regressiontests/expressions_regress/tests.py
@@ -0,0 +1,195 @@
+"""
+Spanning tests for all the operations that F() expressions can perform.
+"""
+from django.test import TestCase, Approximate
+
+from django.conf import settings
+from django.db import models, DEFAULT_DB_ALIAS
+from django.db.models import F
+
+from regressiontests.expressions_regress.models import Number
+
+class ExpressionsRegressTests(TestCase):
+
+ def setUp(self):
+ Number(integer=-1).save()
+ Number(integer=42).save()
+ Number(integer=1337).save()
+ self.assertEqual(Number.objects.update(float=F('integer')), 3)
+
+ def test_fill_with_value_from_same_object(self):
+ """
+ We can fill a value in all objects with an other value of the
+ same object.
+ """
+ self.assertQuerysetEqual(
+ Number.objects.all(),
+ [
+ '<Number: -1, -1.000>',
+ '<Number: 42, 42.000>',
+ '<Number: 1337, 1337.000>'
+ ]
+ )
+
+ def test_increment_value(self):
+ """
+ We can increment a value of all objects in a query set.
+ """
+ self.assertEqual(
+ Number.objects.filter(integer__gt=0)
+ .update(integer=F('integer') + 1),
+ 2)
+
+ self.assertQuerysetEqual(
+ Number.objects.all(),
+ [
+ '<Number: -1, -1.000>',
+ '<Number: 43, 42.000>',
+ '<Number: 1338, 1337.000>'
+ ]
+ )
+
+ def test_filter_not_equals_other_field(self):
+ """
+ We can filter for objects, where a value is not equals the value
+ of an other field.
+ """
+ self.assertEqual(
+ Number.objects.filter(integer__gt=0)
+ .update(integer=F('integer') + 1),
+ 2)
+ self.assertQuerysetEqual(
+ Number.objects.exclude(float=F('integer')),
+ [
+ '<Number: 43, 42.000>',
+ '<Number: 1338, 1337.000>'
+ ]
+ )
+
+ def test_complex_expressions(self):
+ """
+ Complex expressions of different connection types are possible.
+ """
+ n = Number.objects.create(integer=10, float=123.45)
+ self.assertEqual(Number.objects.filter(pk=n.pk)
+ .update(float=F('integer') + F('float') * 2),
+ 1)
+
+ self.assertEqual(Number.objects.get(pk=n.pk).integer, 10)
+ self.assertEqual(Number.objects.get(pk=n.pk).float, Approximate(256.900, places=3))
+
+class ExpressionOperatorTests(TestCase):
+ def setUp(self):
+ self.n = Number.objects.create(integer=42, float=15.5)
+
+ def test_lefthand_addition(self):
+ # LH Addition of floats and integers
+ Number.objects.filter(pk=self.n.pk).update(
+ integer=F('integer') + 15,
+ float=F('float') + 42.7
+ )
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 57)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(58.200, places=3))
+
+ def test_lefthand_subtraction(self):
+ # LH Subtraction of floats and integers
+ Number.objects.filter(pk=self.n.pk).update(integer=F('integer') - 15,
+ float=F('float') - 42.7)
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 27)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(-27.200, places=3))
+
+ def test_lefthand_multiplication(self):
+ # Multiplication of floats and integers
+ Number.objects.filter(pk=self.n.pk).update(integer=F('integer') * 15,
+ float=F('float') * 42.7)
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 630)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(661.850, places=3))
+
+ def test_lefthand_division(self):
+ # LH Division of floats and integers
+ Number.objects.filter(pk=self.n.pk).update(integer=F('integer') / 2,
+ float=F('float') / 42.7)
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 21)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(0.363, places=3))
+
+ def test_lefthand_modulo(self):
+ # LH Modulo arithmetic on integers
+ Number.objects.filter(pk=self.n.pk).update(integer=F('integer') % 20)
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 2)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+
+ def test_lefthand_bitwise_and(self):
+ # LH Bitwise ands on integers
+ Number.objects.filter(pk=self.n.pk).update(integer=F('integer') & 56)
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 40)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
+ def test_lefthand_bitwise_or(self):
+ # LH Bitwise or on integers
+ Number.objects.filter(pk=self.n.pk).update(integer=F('integer') | 48)
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 58)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+
+ def test_right_hand_addition(self):
+ # Right hand operators
+ Number.objects.filter(pk=self.n.pk).update(integer=15 + F('integer'),
+ float=42.7 + F('float'))
+
+ # RH Addition of floats and integers
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 57)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(58.200, places=3))
+
+ def test_right_hand_subtraction(self):
+ Number.objects.filter(pk=self.n.pk).update(integer=15 - F('integer'),
+ float=42.7 - F('float'))
+
+ # RH Subtraction of floats and integers
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, -27)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(27.200, places=3))
+
+ def test_right_hand_multiplication(self):
+ # RH Multiplication of floats and integers
+ Number.objects.filter(pk=self.n.pk).update(integer=15 * F('integer'),
+ float=42.7 * F('float'))
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 630)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(661.850, places=3))
+
+ def test_right_hand_division(self):
+ # RH Division of floats and integers
+ Number.objects.filter(pk=self.n.pk).update(integer=640 / F('integer'),
+ float=42.7 / F('float'))
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 15)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(2.755, places=3))
+
+ def test_right_hand_modulo(self):
+ # RH Modulo arithmetic on integers
+ Number.objects.filter(pk=self.n.pk).update(integer=69 % F('integer'))
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 27)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+
+ def test_right_hand_bitwise_and(self):
+ # RH Bitwise ands on integers
+ Number.objects.filter(pk=self.n.pk).update(integer=15 & F('integer'))
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 10)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
+ def test_right_hand_bitwise_or(self):
+ # RH Bitwise or on integers
+ Number.objects.filter(pk=self.n.pk).update(integer=15 | F('integer'))
+
+ self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 47)
+ self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
+
diff --git a/parts/django/tests/regressiontests/extra_regress/__init__.py b/parts/django/tests/regressiontests/extra_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/extra_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/extra_regress/models.py b/parts/django/tests/regressiontests/extra_regress/models.py
new file mode 100644
index 0000000..073157a
--- /dev/null
+++ b/parts/django/tests/regressiontests/extra_regress/models.py
@@ -0,0 +1,40 @@
+import datetime
+
+import django.utils.copycompat as copy
+
+from django.contrib.auth.models import User
+from django.db import models
+
+class RevisionableModel(models.Model):
+ base = models.ForeignKey('self', null=True)
+ title = models.CharField(blank=True, max_length=255)
+ when = models.DateTimeField(default=datetime.datetime.now)
+
+ def __unicode__(self):
+ return u"%s (%s, %s)" % (self.title, self.id, self.base.id)
+
+ def save(self, *args, **kwargs):
+ super(RevisionableModel, self).save(*args, **kwargs)
+ if not self.base:
+ self.base = self
+ kwargs.pop('force_insert', None)
+ kwargs.pop('force_update', None)
+ super(RevisionableModel, self).save(*args, **kwargs)
+
+ def new_revision(self):
+ new_revision = copy.copy(self)
+ new_revision.pk = None
+ return new_revision
+
+class Order(models.Model):
+ created_by = models.ForeignKey(User)
+ text = models.TextField()
+
+class TestObject(models.Model):
+ first = models.CharField(max_length=20)
+ second = models.CharField(max_length=20)
+ third = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return u'TestObject: %s,%s,%s' % (self.first,self.second,self.third)
+
diff --git a/parts/django/tests/regressiontests/extra_regress/tests.py b/parts/django/tests/regressiontests/extra_regress/tests.py
new file mode 100644
index 0000000..ef7cbb8
--- /dev/null
+++ b/parts/django/tests/regressiontests/extra_regress/tests.py
@@ -0,0 +1,314 @@
+from django.test import TestCase
+
+from django.utils.datastructures import SortedDict
+
+from django.contrib.auth.models import User
+from regressiontests.extra_regress.models import TestObject, Order, \
+ RevisionableModel
+
+import datetime
+
+class ExtraRegressTests(TestCase):
+
+ def setUp(self):
+ self.u = User.objects.create_user(
+ username="fred",
+ password="secret",
+ email="fred@example.com"
+ )
+
+ def test_regression_7314_7372(self):
+ """
+ Regression tests for #7314 and #7372
+ """
+ rm = RevisionableModel.objects.create(
+ title='First Revision',
+ when=datetime.datetime(2008, 9, 28, 10, 30, 0)
+ )
+ self.assertEqual(rm.pk, rm.base.pk)
+
+ rm2 = rm.new_revision()
+ rm2.title = "Second Revision"
+ rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0)
+ rm2.save()
+
+ self.assertEqual(rm2.title, 'Second Revision')
+ self.assertEqual(rm2.base.title, 'First Revision')
+
+ self.assertNotEqual(rm2.pk, rm.pk)
+ self.assertEqual(rm2.base.pk, rm.pk)
+
+ # Queryset to match most recent revision:
+ qs = RevisionableModel.objects.extra(
+ where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % {
+ 'table': RevisionableModel._meta.db_table,
+ }]
+ )
+
+ self.assertQuerysetEqual(qs,
+ [('Second Revision', 'First Revision')],
+ transform=lambda r: (r.title, r.base.title)
+ )
+
+ # Queryset to search for string in title:
+ qs2 = RevisionableModel.objects.filter(title__contains="Revision")
+ self.assertQuerysetEqual(qs2,
+ [
+ ('First Revision', 'First Revision'),
+ ('Second Revision', 'First Revision'),
+ ],
+ transform=lambda r: (r.title, r.base.title)
+ )
+
+ # Following queryset should return the most recent revision:
+ self.assertQuerysetEqual(qs & qs2,
+ [('Second Revision', 'First Revision')],
+ transform=lambda r: (r.title, r.base.title)
+ )
+
+ def test_extra_stay_tied(self):
+ # Extra select parameters should stay tied to their corresponding
+ # select portions. Applies when portions are updated or otherwise
+ # moved around.
+ qs = User.objects.extra(
+ select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))),
+ select_params=(1, 3)
+ )
+ qs = qs.extra(select={"beta": 4})
+ qs = qs.extra(select={"alpha": "%s"}, select_params=[5])
+ self.assertEqual(
+ list(qs.filter(id=self.u.id).values('alpha', 'beta', 'gamma')),
+ [{'alpha': 5, 'beta': 4, 'gamma': 3}]
+ )
+
+ def test_regression_7957(self):
+ """
+ Regression test for #7957: Combining extra() calls should leave the
+ corresponding parameters associated with the right extra() bit. I.e.
+ internal dictionary must remain sorted.
+ """
+ self.assertEqual(
+ User.objects.extra(select={"alpha": "%s"}, select_params=(1,)
+ ).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha,
+ 1)
+
+ self.assertEqual(
+ User.objects.extra(select={"beta": "%s"}, select_params=(1,)
+ ).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha,
+ 2)
+
+ def test_regression_7961(self):
+ """
+ Regression test for #7961: When not using a portion of an
+ extra(...) in a query, remove any corresponding parameters from the
+ query as well.
+ """
+ self.assertEqual(
+ list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,)
+ ).filter(id=self.u.id).values_list('id', flat=True)),
+ [self.u.id]
+ )
+
+ def test_regression_8063(self):
+ """
+ Regression test for #8063: limiting a query shouldn't discard any
+ extra() bits.
+ """
+ qs = User.objects.all().extra(where=['id=%s'], params=[self.u.id])
+ self.assertQuerysetEqual(qs, ['<User: fred>'])
+ self.assertQuerysetEqual(qs[:1], ['<User: fred>'])
+
+ def test_regression_8039(self):
+ """
+ Regression test for #8039: Ordering sometimes removed relevant tables
+ from extra(). This test is the critical case: ordering uses a table,
+ but then removes the reference because of an optimisation. The table
+ should still be present because of the extra() call.
+ """
+ self.assertQuerysetEqual(
+ Order.objects.extra(where=["username=%s"],
+ params=["fred"],
+ tables=["auth_user"]
+ ).order_by('created_by'),
+ []
+ )
+
+ def test_regression_8819(self):
+ """
+ Regression test for #8819: Fields in the extra(select=...) list
+ should be available to extra(order_by=...).
+ """
+ self.assertQuerysetEqual(
+ User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}).distinct(),
+ ['<User: fred>']
+ )
+ self.assertQuerysetEqual(
+ User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']),
+ ['<User: fred>']
+ )
+ self.assertQuerysetEqual(
+ User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct(),
+ ['<User: fred>']
+ )
+
+ def test_dates_query(self):
+ """
+ When calling the dates() method on a queryset with extra selection
+ columns, we can (and should) ignore those columns. They don't change
+ the result and cause incorrect SQL to be produced otherwise.
+ """
+ rm = RevisionableModel.objects.create(
+ title='First Revision',
+ when=datetime.datetime(2008, 9, 28, 10, 30, 0)
+ )
+
+ self.assertQuerysetEqual(
+ RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month'),
+ ['datetime.datetime(2008, 9, 1, 0, 0)']
+ )
+
+ def test_values_with_extra(self):
+ """
+ Regression test for #10256... If there is a values() clause, Extra
+ columns are only returned if they are explicitly mentioned.
+ """
+ obj = TestObject(first='first', second='second', third='third')
+ obj.save()
+
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values()),
+ [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}]
+ )
+
+ # Extra clauses after an empty values clause are still included
+ self.assertEqual(
+ list(TestObject.objects.values().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))),
+ [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}]
+ )
+
+ # Extra columns are ignored if not mentioned in the values() clause
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second')),
+ [{'second': u'second', 'first': u'first'}]
+ )
+
+ # Extra columns after a non-empty values() clause are ignored
+ self.assertEqual(
+ list(TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))),
+ [{'second': u'second', 'first': u'first'}]
+ )
+
+ # Extra columns can be partially returned
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second', 'foo')),
+ [{'second': u'second', 'foo': u'first', 'first': u'first'}]
+ )
+
+ # Also works if only extra columns are included
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('foo', 'whiz')),
+ [{'foo': u'first', 'whiz': u'third'}]
+ )
+
+ # Values list works the same way
+ # All columns are returned for an empty values_list()
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()),
+ [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')]
+ )
+
+ # Extra columns after an empty values_list() are still included
+ self.assertEqual(
+ list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))),
+ [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')]
+ )
+
+ # Extra columns ignored completely if not mentioned in values_list()
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')),
+ [(u'first', u'second')]
+ )
+
+ # Extra columns after a non-empty values_list() clause are ignored completely
+ self.assertEqual(
+ list(TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))),
+ [(u'first', u'second')]
+ )
+
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('second', flat=True)),
+ [u'second']
+ )
+
+ # Only the extra columns specified in the values_list() are returned
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second', 'whiz')),
+ [(u'first', u'second', u'third')]
+ )
+
+ # ...also works if only extra columns are included
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('foo','whiz')),
+ [(u'first', u'third')]
+ )
+
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', flat=True)),
+ [u'third']
+ )
+
+ # ... and values are returned in the order they are specified
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz','foo')),
+ [(u'third', u'first')]
+ )
+
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first','id')),
+ [(u'first', obj.pk)]
+ )
+
+ self.assertEqual(
+ list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')),
+ [(u'third', u'first', u'second', obj.pk)]
+ )
+
+ def test_regression_10847(self):
+ """
+ Regression for #10847: the list of extra columns can always be
+ accurately evaluated. Using an inner query ensures that as_sql() is
+ producing correct output without requiring full evaluation and
+ execution of the inner query.
+ """
+ obj = TestObject(first='first', second='second', third='third')
+ obj.save()
+
+ self.assertEqual(
+ list(TestObject.objects.extra(select={'extra': 1}).values('pk')),
+ [{'pk': obj.pk}]
+ )
+
+ self.assertQuerysetEqual(
+ TestObject.objects.filter(
+ pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk')
+ ),
+ ['<TestObject: TestObject: first,second,third>']
+ )
+
+ self.assertEqual(
+ list(TestObject.objects.values('pk').extra(select={'extra': 1})),
+ [{'pk': obj.pk}]
+ )
+
+ self.assertQuerysetEqual(
+ TestObject.objects.filter(
+ pk__in=TestObject.objects.values('pk').extra(select={'extra': 1})
+ ),
+ ['<TestObject: TestObject: first,second,third>']
+ )
+
+ self.assertQuerysetEqual(
+ TestObject.objects.filter(pk=obj.pk) |
+ TestObject.objects.extra(where=["id > %s"], params=[obj.pk]),
+ ['<TestObject: TestObject: first,second,third>']
+ )
diff --git a/parts/django/tests/regressiontests/file_storage/__init__.py b/parts/django/tests/regressiontests/file_storage/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_storage/__init__.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/file_storage/models.py b/parts/django/tests/regressiontests/file_storage/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_storage/models.py
diff --git a/parts/django/tests/regressiontests/file_storage/test.png b/parts/django/tests/regressiontests/file_storage/test.png
new file mode 100644
index 0000000..4f17cd0
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_storage/test.png
Binary files differ
diff --git a/parts/django/tests/regressiontests/file_storage/test1.png b/parts/django/tests/regressiontests/file_storage/test1.png
new file mode 100644
index 0000000..bc98741
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_storage/test1.png
Binary files differ
diff --git a/parts/django/tests/regressiontests/file_storage/tests.py b/parts/django/tests/regressiontests/file_storage/tests.py
new file mode 100644
index 0000000..8726181
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_storage/tests.py
@@ -0,0 +1,382 @@
+# -*- coding: utf-8 -*-
+import os
+import shutil
+import sys
+import tempfile
+import time
+import unittest
+from cStringIO import StringIO
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
+from django.core.files.base import ContentFile, File
+from django.core.files.images import get_image_dimensions
+from django.core.files.storage import FileSystemStorage, get_storage_class
+from django.core.files.uploadedfile import UploadedFile
+from unittest import TestCase
+
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
+
+# Try to import PIL in either of the two ways it can end up installed.
+# Checking for the existence of Image is enough for CPython, but
+# for PyPy, you need to check for the underlying modules
+try:
+ from PIL import Image, _imaging
+except ImportError:
+ try:
+ import Image, _imaging
+ except ImportError:
+ Image = None
+
+class GetStorageClassTests(unittest.TestCase):
+ def assertRaisesErrorWithMessage(self, error, message, callable,
+ *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ def test_get_filesystem_storage(self):
+ """
+ get_storage_class returns the class for a storage backend name/path.
+ """
+ self.assertEqual(
+ get_storage_class('django.core.files.storage.FileSystemStorage'),
+ FileSystemStorage)
+
+ def test_get_invalid_storage_module(self):
+ """
+ get_storage_class raises an error if the requested import don't exist.
+ """
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "NonExistingStorage isn't a storage module.",
+ get_storage_class,
+ 'NonExistingStorage')
+
+ def test_get_nonexisting_storage_class(self):
+ """
+ get_storage_class raises an error if the requested class don't exist.
+ """
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ 'Storage module "django.core.files.storage" does not define a '\
+ '"NonExistingStorage" class.',
+ get_storage_class,
+ 'django.core.files.storage.NonExistingStorage')
+
+ def test_get_nonexisting_storage_module(self):
+ """
+ get_storage_class raises an error if the requested module don't exist.
+ """
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ 'Error importing storage module django.core.files.non_existing_'\
+ 'storage: "No module named non_existing_storage"',
+ get_storage_class,
+ 'django.core.files.non_existing_storage.NonExistingStorage')
+
+class FileStorageTests(unittest.TestCase):
+ storage_class = FileSystemStorage
+
+ def setUp(self):
+ self.temp_dir = tempfile.mktemp()
+ os.makedirs(self.temp_dir)
+ self.storage = self.storage_class(location=self.temp_dir,
+ base_url='/test_media_url/')
+
+ def tearDown(self):
+ shutil.rmtree(self.temp_dir)
+
+ def test_file_access_options(self):
+ """
+ Standard file access options are available, and work as expected.
+ """
+ self.assertFalse(self.storage.exists('storage_test'))
+ f = self.storage.open('storage_test', 'w')
+ f.write('storage contents')
+ f.close()
+ self.assert_(self.storage.exists('storage_test'))
+
+ f = self.storage.open('storage_test', 'r')
+ self.assertEqual(f.read(), 'storage contents')
+ f.close()
+
+ self.storage.delete('storage_test')
+ self.assertFalse(self.storage.exists('storage_test'))
+
+ def test_file_save_without_name(self):
+ """
+ File storage extracts the filename from the content object if no
+ name is given explicitly.
+ """
+ self.assertFalse(self.storage.exists('test.file'))
+
+ f = ContentFile('custom contents')
+ f.name = 'test.file'
+
+ storage_f_name = self.storage.save(None, f)
+
+ self.assertEqual(storage_f_name, f.name)
+
+ self.assert_(os.path.exists(os.path.join(self.temp_dir, f.name)))
+
+ self.storage.delete(storage_f_name)
+
+ def test_file_path(self):
+ """
+ File storage returns the full path of a file
+ """
+ self.assertFalse(self.storage.exists('test.file'))
+
+ f = ContentFile('custom contents')
+ f_name = self.storage.save('test.file', f)
+
+ self.assertEqual(self.storage.path(f_name),
+ os.path.join(self.temp_dir, f_name))
+
+ self.storage.delete(f_name)
+
+ def test_file_url(self):
+ """
+ File storage returns a url to access a given file from the Web.
+ """
+ self.assertEqual(self.storage.url('test.file'),
+ '%s%s' % (self.storage.base_url, 'test.file'))
+
+ self.storage.base_url = None
+ self.assertRaises(ValueError, self.storage.url, 'test.file')
+
+ def test_file_with_mixin(self):
+ """
+ File storage can get a mixin to extend the functionality of the
+ returned file.
+ """
+ self.assertFalse(self.storage.exists('test.file'))
+
+ class TestFileMixin(object):
+ mixed_in = True
+
+ f = ContentFile('custom contents')
+ f_name = self.storage.save('test.file', f)
+
+ self.assert_(isinstance(
+ self.storage.open('test.file', mixin=TestFileMixin),
+ TestFileMixin
+ ))
+
+ self.storage.delete('test.file')
+
+ def test_listdir(self):
+ """
+ File storage returns a tuple containing directories and files.
+ """
+ self.assertFalse(self.storage.exists('storage_test_1'))
+ self.assertFalse(self.storage.exists('storage_test_2'))
+ self.assertFalse(self.storage.exists('storage_dir_1'))
+
+ f = self.storage.save('storage_test_1', ContentFile('custom content'))
+ f = self.storage.save('storage_test_2', ContentFile('custom content'))
+ os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1'))
+
+ dirs, files = self.storage.listdir('')
+ self.assertEqual(set(dirs), set([u'storage_dir_1']))
+ self.assertEqual(set(files),
+ set([u'storage_test_1', u'storage_test_2']))
+
+ self.storage.delete('storage_test_1')
+ self.storage.delete('storage_test_2')
+ os.rmdir(os.path.join(self.temp_dir, 'storage_dir_1'))
+
+ def test_file_storage_prevents_directory_traversal(self):
+ """
+ File storage prevents directory traversal (files can only be accessed if
+ they're below the storage location).
+ """
+ self.assertRaises(SuspiciousOperation, self.storage.exists, '..')
+ self.assertRaises(SuspiciousOperation, self.storage.exists, '/etc/passwd')
+
+class CustomStorage(FileSystemStorage):
+ def get_available_name(self, name):
+ """
+ Append numbers to duplicate files rather than underscores, like Trac.
+ """
+ parts = name.split('.')
+ basename, ext = parts[0], parts[1:]
+ number = 2
+ while self.exists(name):
+ name = '.'.join([basename, str(number)] + ext)
+ number += 1
+
+ return name
+
+class CustomStorageTests(FileStorageTests):
+ storage_class = CustomStorage
+
+ def test_custom_get_available_name(self):
+ first = self.storage.save('custom_storage', ContentFile('custom contents'))
+ self.assertEqual(first, 'custom_storage')
+ second = self.storage.save('custom_storage', ContentFile('more contents'))
+ self.assertEqual(second, 'custom_storage.2')
+ self.storage.delete(first)
+ self.storage.delete(second)
+
+class UnicodeFileNameTests(unittest.TestCase):
+ def test_unicode_file_names(self):
+ """
+ Regression test for #8156: files with unicode names I can't quite figure
+ out the encoding situation between doctest and this file, but the actual
+ repr doesn't matter; it just shouldn't return a unicode object.
+ """
+ uf = UploadedFile(name=u'¿Cómo?',content_type='text')
+ self.assertEqual(type(uf.__repr__()), str)
+
+# Tests for a race condition on file saving (#4948).
+# This is written in such a way that it'll always pass on platforms
+# without threading.
+
+class SlowFile(ContentFile):
+ def chunks(self):
+ time.sleep(1)
+ return super(ContentFile, self).chunks()
+
+class FileSaveRaceConditionTest(TestCase):
+ def setUp(self):
+ self.storage_dir = tempfile.mkdtemp()
+ self.storage = FileSystemStorage(self.storage_dir)
+ self.thread = threading.Thread(target=self.save_file, args=['conflict'])
+
+ def tearDown(self):
+ shutil.rmtree(self.storage_dir)
+
+ def save_file(self, name):
+ name = self.storage.save(name, SlowFile("Data"))
+
+ def test_race_condition(self):
+ self.thread.start()
+ name = self.save_file('conflict')
+ self.thread.join()
+ self.assert_(self.storage.exists('conflict'))
+ self.assert_(self.storage.exists('conflict_1'))
+ self.storage.delete('conflict')
+ self.storage.delete('conflict_1')
+
+class FileStoragePermissions(TestCase):
+ def setUp(self):
+ self.old_perms = settings.FILE_UPLOAD_PERMISSIONS
+ settings.FILE_UPLOAD_PERMISSIONS = 0666
+ self.storage_dir = tempfile.mkdtemp()
+ self.storage = FileSystemStorage(self.storage_dir)
+
+ def tearDown(self):
+ settings.FILE_UPLOAD_PERMISSIONS = self.old_perms
+ shutil.rmtree(self.storage_dir)
+
+ def test_file_upload_permissions(self):
+ name = self.storage.save("the_file", ContentFile("data"))
+ actual_mode = os.stat(self.storage.path(name))[0] & 0777
+ self.assertEqual(actual_mode, 0666)
+
+
+class FileStoragePathParsing(TestCase):
+ def setUp(self):
+ self.storage_dir = tempfile.mkdtemp()
+ self.storage = FileSystemStorage(self.storage_dir)
+
+ def tearDown(self):
+ shutil.rmtree(self.storage_dir)
+
+ def test_directory_with_dot(self):
+ """Regression test for #9610.
+
+ If the directory name contains a dot and the file name doesn't, make
+ sure we still mangle the file name instead of the directory name.
+ """
+
+ self.storage.save('dotted.path/test', ContentFile("1"))
+ self.storage.save('dotted.path/test', ContentFile("2"))
+
+ self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
+ self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
+ self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1')))
+
+ def test_first_character_dot(self):
+ """
+ File names with a dot as their first character don't have an extension,
+ and the underscore should get added to the end.
+ """
+ self.storage.save('dotted.path/.test', ContentFile("1"))
+ self.storage.save('dotted.path/.test', ContentFile("2"))
+
+ self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test')))
+ # Before 2.6, a leading dot was treated as an extension, and so
+ # underscore gets added to beginning instead of end.
+ if sys.version_info < (2, 6):
+ self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test')))
+ else:
+ self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
+
+if Image is not None:
+ class DimensionClosingBug(TestCase):
+ """
+ Test that get_image_dimensions() properly closes files (#8817)
+ """
+ def test_not_closing_of_files(self):
+ """
+ Open files passed into get_image_dimensions() should stay opened.
+ """
+ empty_io = StringIO()
+ try:
+ get_image_dimensions(empty_io)
+ finally:
+ self.assert_(not empty_io.closed)
+
+ def test_closing_of_filenames(self):
+ """
+ get_image_dimensions() called with a filename should closed the file.
+ """
+ # We need to inject a modified open() builtin into the images module
+ # that checks if the file was closed properly if the function is
+ # called with a filename instead of an file object.
+ # get_image_dimensions will call our catching_open instead of the
+ # regular builtin one.
+
+ class FileWrapper(object):
+ _closed = []
+ def __init__(self, f):
+ self.f = f
+ def __getattr__(self, name):
+ return getattr(self.f, name)
+ def close(self):
+ self._closed.append(True)
+ self.f.close()
+
+ def catching_open(*args):
+ return FileWrapper(open(*args))
+
+ from django.core.files import images
+ images.open = catching_open
+ try:
+ get_image_dimensions(os.path.join(os.path.dirname(__file__), "test1.png"))
+ finally:
+ del images.open
+ self.assert_(FileWrapper._closed)
+
+ class InconsistentGetImageDimensionsBug(TestCase):
+ """
+ Test that get_image_dimensions() works properly after various calls using a file handler (#11158)
+ """
+ def test_multiple_calls(self):
+ """
+ Multiple calls of get_image_dimensions() should return the same size.
+ """
+ from django.core.files.images import ImageFile
+ img_path = os.path.join(os.path.dirname(__file__), "test.png")
+ image = ImageFile(open(img_path, 'rb'))
+ image_pil = Image.open(img_path)
+ size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image)
+ self.assertEqual(image_pil.size, size_1)
+ self.assertEqual(size_1, size_2)
diff --git a/parts/django/tests/regressiontests/file_uploads/__init__.py b/parts/django/tests/regressiontests/file_uploads/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_uploads/__init__.py
diff --git a/parts/django/tests/regressiontests/file_uploads/models.py b/parts/django/tests/regressiontests/file_uploads/models.py
new file mode 100644
index 0000000..9d02050
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_uploads/models.py
@@ -0,0 +1,10 @@
+import tempfile
+import os
+from django.db import models
+from django.core.files.storage import FileSystemStorage
+
+temp_storage = FileSystemStorage(tempfile.mkdtemp())
+UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload')
+
+class FileModel(models.Model):
+ testfile = models.FileField(storage=temp_storage, upload_to='test_upload')
diff --git a/parts/django/tests/regressiontests/file_uploads/tests.py b/parts/django/tests/regressiontests/file_uploads/tests.py
new file mode 100644
index 0000000..99d0b6b
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_uploads/tests.py
@@ -0,0 +1,305 @@
+#! -*- coding: utf-8 -*-
+import errno
+import os
+import shutil
+import unittest
+from StringIO import StringIO
+
+from django.core.files import temp as tempfile
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.test import TestCase, client
+from django.utils import simplejson
+from django.utils.hashcompat import sha_constructor
+from django.http.multipartparser import MultiPartParser
+
+from models import FileModel, temp_storage, UPLOAD_TO
+import uploadhandler
+
+
+UNICODE_FILENAME = u'test-0123456789_中文_Orléans.jpg'
+
+class FileUploadTests(TestCase):
+ def test_simple_upload(self):
+ post_data = {
+ 'name': 'Ringo',
+ 'file_field': open(__file__),
+ }
+ response = self.client.post('/file_uploads/upload/', post_data)
+ self.assertEqual(response.status_code, 200)
+
+ def test_large_upload(self):
+ tdir = tempfile.gettempdir()
+
+ file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
+ file1.write('a' * (2 ** 21))
+ file1.seek(0)
+
+ file2 = tempfile.NamedTemporaryFile(suffix=".file2", dir=tdir)
+ file2.write('a' * (10 * 2 ** 20))
+ file2.seek(0)
+
+ post_data = {
+ 'name': 'Ringo',
+ 'file_field1': file1,
+ 'file_field2': file2,
+ }
+
+ for key in post_data.keys():
+ try:
+ post_data[key + '_hash'] = sha_constructor(post_data[key].read()).hexdigest()
+ post_data[key].seek(0)
+ except AttributeError:
+ post_data[key + '_hash'] = sha_constructor(post_data[key]).hexdigest()
+
+ response = self.client.post('/file_uploads/verify/', post_data)
+
+ self.assertEqual(response.status_code, 200)
+
+ def test_unicode_file_name(self):
+ tdir = tempfile.gettempdir()
+
+ # This file contains chinese symbols and an accented char in the name.
+ file1 = open(os.path.join(tdir, UNICODE_FILENAME.encode('utf-8')), 'w+b')
+ file1.write('b' * (2 ** 10))
+ file1.seek(0)
+
+ post_data = {
+ 'file_unicode': file1,
+ }
+
+ response = self.client.post('/file_uploads/unicode_name/', post_data)
+
+ file1.close()
+ try:
+ os.unlink(file1.name)
+ except:
+ pass
+
+ self.assertEqual(response.status_code, 200)
+
+ def test_dangerous_file_names(self):
+ """Uploaded file names should be sanitized before ever reaching the view."""
+ # This test simulates possible directory traversal attacks by a
+ # malicious uploader We have to do some monkeybusiness here to construct
+ # a malicious payload with an invalid file name (containing os.sep or
+ # os.pardir). This similar to what an attacker would need to do when
+ # trying such an attack.
+ scary_file_names = [
+ "/tmp/hax0rd.txt", # Absolute path, *nix-style.
+ "C:\\Windows\\hax0rd.txt", # Absolute path, win-syle.
+ "C:/Windows/hax0rd.txt", # Absolute path, broken-style.
+ "\\tmp\\hax0rd.txt", # Absolute path, broken in a different way.
+ "/tmp\\hax0rd.txt", # Absolute path, broken by mixing.
+ "subdir/hax0rd.txt", # Descendant path, *nix-style.
+ "subdir\\hax0rd.txt", # Descendant path, win-style.
+ "sub/dir\\hax0rd.txt", # Descendant path, mixed.
+ "../../hax0rd.txt", # Relative path, *nix-style.
+ "..\\..\\hax0rd.txt", # Relative path, win-style.
+ "../..\\hax0rd.txt" # Relative path, mixed.
+ ]
+
+ payload = []
+ for i, name in enumerate(scary_file_names):
+ payload.extend([
+ '--' + client.BOUNDARY,
+ 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name),
+ 'Content-Type: application/octet-stream',
+ '',
+ 'You got pwnd.'
+ ])
+ payload.extend([
+ '--' + client.BOUNDARY + '--',
+ '',
+ ])
+
+ payload = "\r\n".join(payload)
+ r = {
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': client.MULTIPART_CONTENT,
+ 'PATH_INFO': "/file_uploads/echo/",
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': client.FakePayload(payload),
+ }
+ response = self.client.request(**r)
+
+ # The filenames should have been sanitized by the time it got to the view.
+ recieved = simplejson.loads(response.content)
+ for i, name in enumerate(scary_file_names):
+ got = recieved["file%s" % i]
+ self.assertEqual(got, "hax0rd.txt")
+
+ def test_filename_overflow(self):
+ """File names over 256 characters (dangerous on some platforms) get fixed up."""
+ name = "%s.txt" % ("f"*500)
+ payload = "\r\n".join([
+ '--' + client.BOUNDARY,
+ 'Content-Disposition: form-data; name="file"; filename="%s"' % name,
+ 'Content-Type: application/octet-stream',
+ '',
+ 'Oops.'
+ '--' + client.BOUNDARY + '--',
+ '',
+ ])
+ r = {
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': client.MULTIPART_CONTENT,
+ 'PATH_INFO': "/file_uploads/echo/",
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': client.FakePayload(payload),
+ }
+ got = simplejson.loads(self.client.request(**r).content)
+ self.assert_(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
+
+ def test_custom_upload_handler(self):
+ # A small file (under the 5M quota)
+ smallfile = tempfile.NamedTemporaryFile()
+ smallfile.write('a' * (2 ** 21))
+ smallfile.seek(0)
+
+ # A big file (over the quota)
+ bigfile = tempfile.NamedTemporaryFile()
+ bigfile.write('a' * (10 * 2 ** 20))
+ bigfile.seek(0)
+
+ # Small file posting should work.
+ response = self.client.post('/file_uploads/quota/', {'f': smallfile})
+ got = simplejson.loads(response.content)
+ self.assert_('f' in got)
+
+ # Large files don't go through.
+ response = self.client.post("/file_uploads/quota/", {'f': bigfile})
+ got = simplejson.loads(response.content)
+ self.assert_('f' not in got)
+
+ def test_broken_custom_upload_handler(self):
+ f = tempfile.NamedTemporaryFile()
+ f.write('a' * (2 ** 21))
+ f.seek(0)
+
+ # AttributeError: You cannot alter upload handlers after the upload has been processed.
+ self.assertRaises(
+ AttributeError,
+ self.client.post,
+ '/file_uploads/quota/broken/',
+ {'f': f}
+ )
+
+ def test_fileupload_getlist(self):
+ file1 = tempfile.NamedTemporaryFile()
+ file1.write('a' * (2 ** 23))
+ file1.seek(0)
+
+ file2 = tempfile.NamedTemporaryFile()
+ file2.write('a' * (2 * 2 ** 18))
+ file2.seek(0)
+
+ file2a = tempfile.NamedTemporaryFile()
+ file2a.write('a' * (5 * 2 ** 20))
+ file2a.seek(0)
+
+ response = self.client.post('/file_uploads/getlist_count/', {
+ 'file1': file1,
+ 'field1': u'test',
+ 'field2': u'test3',
+ 'field3': u'test5',
+ 'field4': u'test6',
+ 'field5': u'test7',
+ 'file2': (file2, file2a)
+ })
+ got = simplejson.loads(response.content)
+
+ self.assertEqual(got.get('file1'), 1)
+ self.assertEqual(got.get('file2'), 2)
+
+ def test_file_error_blocking(self):
+ """
+ The server should not block when there are upload errors (bug #8622).
+ This can happen if something -- i.e. an exception handler -- tries to
+ access POST while handling an error in parsing POST. This shouldn't
+ cause an infinite loop!
+ """
+ class POSTAccessingHandler(client.ClientHandler):
+ """A handler that'll access POST during an exception."""
+ def handle_uncaught_exception(self, request, resolver, exc_info):
+ ret = super(POSTAccessingHandler, self).handle_uncaught_exception(request, resolver, exc_info)
+ p = request.POST
+ return ret
+
+ post_data = {
+ 'name': 'Ringo',
+ 'file_field': open(__file__),
+ }
+ # Maybe this is a little more complicated that it needs to be; but if
+ # the django.test.client.FakePayload.read() implementation changes then
+ # this test would fail. So we need to know exactly what kind of error
+ # it raises when there is an attempt to read more than the available bytes:
+ try:
+ client.FakePayload('a').read(2)
+ except Exception, reference_error:
+ pass
+
+ # install the custom handler that tries to access request.POST
+ self.client.handler = POSTAccessingHandler()
+
+ try:
+ response = self.client.post('/file_uploads/upload_errors/', post_data)
+ except reference_error.__class__, err:
+ self.failIf(
+ str(err) == str(reference_error),
+ "Caught a repeated exception that'll cause an infinite loop in file uploads."
+ )
+ except Exception, err:
+ # CustomUploadError is the error that should have been raised
+ self.assertEqual(err.__class__, uploadhandler.CustomUploadError)
+
+class DirectoryCreationTests(unittest.TestCase):
+ """
+ Tests for error handling during directory creation
+ via _save_FIELD_file (ticket #6450)
+ """
+ def setUp(self):
+ self.obj = FileModel()
+ if not os.path.isdir(temp_storage.location):
+ os.makedirs(temp_storage.location)
+ if os.path.isdir(UPLOAD_TO):
+ os.chmod(UPLOAD_TO, 0700)
+ shutil.rmtree(UPLOAD_TO)
+
+ def tearDown(self):
+ os.chmod(temp_storage.location, 0700)
+ shutil.rmtree(temp_storage.location)
+
+ def test_readonly_root(self):
+ """Permission errors are not swallowed"""
+ os.chmod(temp_storage.location, 0500)
+ try:
+ self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', 'x'))
+ except OSError, err:
+ self.assertEquals(err.errno, errno.EACCES)
+ except Exception, err:
+ self.fail("OSError [Errno %s] not raised." % errno.EACCES)
+
+ def test_not_a_directory(self):
+ """The correct IOError is raised when the upload directory name exists but isn't a directory"""
+ # Create a file with the upload directory name
+ fd = open(UPLOAD_TO, 'w')
+ fd.close()
+ try:
+ self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', 'x'))
+ except IOError, err:
+ # The test needs to be done on a specific string as IOError
+ # is raised even without the patch (just not early enough)
+ self.assertEquals(err.args[0],
+ "%s exists and is not a directory." % UPLOAD_TO)
+ except:
+ self.fail("IOError not raised")
+
+class MultiParserTests(unittest.TestCase):
+
+ def test_empty_upload_handlers(self):
+ # We're not actually parsing here; just checking if the parser properly
+ # instantiates with empty upload handlers.
+ parser = MultiPartParser({
+ 'CONTENT_TYPE': 'multipart/form-data; boundary=_foo',
+ 'CONTENT_LENGTH': '1'
+ }, StringIO('x'), [], 'utf-8')
diff --git a/parts/django/tests/regressiontests/file_uploads/uploadhandler.py b/parts/django/tests/regressiontests/file_uploads/uploadhandler.py
new file mode 100644
index 0000000..9f3960a
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_uploads/uploadhandler.py
@@ -0,0 +1,34 @@
+"""
+Upload handlers to test the upload API.
+"""
+
+from django.core.files.uploadhandler import FileUploadHandler, StopUpload
+
+class QuotaUploadHandler(FileUploadHandler):
+ """
+ This test upload handler terminates the connection if more than a quota
+ (5MB) is uploaded.
+ """
+
+ QUOTA = 5 * 2**20 # 5 MB
+
+ def __init__(self, request=None):
+ super(QuotaUploadHandler, self).__init__(request)
+ self.total_upload = 0
+
+ def receive_data_chunk(self, raw_data, start):
+ self.total_upload += len(raw_data)
+ if self.total_upload >= self.QUOTA:
+ raise StopUpload(connection_reset=True)
+ return raw_data
+
+ def file_complete(self, file_size):
+ return None
+
+class CustomUploadError(Exception):
+ pass
+
+class ErroringUploadHandler(FileUploadHandler):
+ """A handler that raises an exception."""
+ def receive_data_chunk(self, raw_data, start):
+ raise CustomUploadError("Oops!")
diff --git a/parts/django/tests/regressiontests/file_uploads/urls.py b/parts/django/tests/regressiontests/file_uploads/urls.py
new file mode 100644
index 0000000..413080e
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_uploads/urls.py
@@ -0,0 +1,13 @@
+from django.conf.urls.defaults import *
+import views
+
+urlpatterns = patterns('',
+ (r'^upload/$', views.file_upload_view),
+ (r'^verify/$', views.file_upload_view_verify),
+ (r'^unicode_name/$', views.file_upload_unicode_name),
+ (r'^echo/$', views.file_upload_echo),
+ (r'^quota/$', views.file_upload_quota),
+ (r'^quota/broken/$', views.file_upload_quota_broken),
+ (r'^getlist_count/$', views.file_upload_getlist_count),
+ (r'^upload_errors/$', views.file_upload_errors),
+)
diff --git a/parts/django/tests/regressiontests/file_uploads/views.py b/parts/django/tests/regressiontests/file_uploads/views.py
new file mode 100644
index 0000000..50bc3f8
--- /dev/null
+++ b/parts/django/tests/regressiontests/file_uploads/views.py
@@ -0,0 +1,114 @@
+import os
+from django.core.files.uploadedfile import UploadedFile
+from django.http import HttpResponse, HttpResponseServerError
+from django.utils import simplejson
+from models import FileModel, UPLOAD_TO
+from uploadhandler import QuotaUploadHandler, ErroringUploadHandler
+from django.utils.hashcompat import sha_constructor
+from tests import UNICODE_FILENAME
+
+def file_upload_view(request):
+ """
+ Check that a file upload can be updated into the POST dictionary without
+ going pear-shaped.
+ """
+ form_data = request.POST.copy()
+ form_data.update(request.FILES)
+ if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode):
+ # If a file is posted, the dummy client should only post the file name,
+ # not the full path.
+ if os.path.dirname(form_data['file_field'].name) != '':
+ return HttpResponseServerError()
+ return HttpResponse('')
+ else:
+ return HttpResponseServerError()
+
+def file_upload_view_verify(request):
+ """
+ Use the sha digest hash to verify the uploaded contents.
+ """
+ form_data = request.POST.copy()
+ form_data.update(request.FILES)
+
+ for key, value in form_data.items():
+ if key.endswith('_hash'):
+ continue
+ if key + '_hash' not in form_data:
+ continue
+ submitted_hash = form_data[key + '_hash']
+ if isinstance(value, UploadedFile):
+ new_hash = sha_constructor(value.read()).hexdigest()
+ else:
+ new_hash = sha_constructor(value).hexdigest()
+ if new_hash != submitted_hash:
+ return HttpResponseServerError()
+
+ # Adding large file to the database should succeed
+ largefile = request.FILES['file_field2']
+ obj = FileModel()
+ obj.testfile.save(largefile.name, largefile)
+
+ return HttpResponse('')
+
+def file_upload_unicode_name(request):
+
+ # Check to see if unicode name came through properly.
+ if not request.FILES['file_unicode'].name.endswith(UNICODE_FILENAME):
+ return HttpResponseServerError()
+
+ response = None
+
+ # Check to make sure the exotic characters are preserved even
+ # through file save.
+ uni_named_file = request.FILES['file_unicode']
+ obj = FileModel.objects.create(testfile=uni_named_file)
+ full_name = u'%s/%s' % (UPLOAD_TO, uni_named_file.name)
+ if not os.path.exists(full_name):
+ response = HttpResponseServerError()
+
+ # Cleanup the object with its exotic file name immediately.
+ # (shutil.rmtree used elsewhere in the tests to clean up the
+ # upload directory has been seen to choke on unicode
+ # filenames on Windows.)
+ obj.delete()
+
+ if response:
+ return response
+ else:
+ return HttpResponse('')
+
+def file_upload_echo(request):
+ """
+ Simple view to echo back info about uploaded files for tests.
+ """
+ r = dict([(k, f.name) for k, f in request.FILES.items()])
+ return HttpResponse(simplejson.dumps(r))
+
+def file_upload_quota(request):
+ """
+ Dynamically add in an upload handler.
+ """
+ request.upload_handlers.insert(0, QuotaUploadHandler())
+ return file_upload_echo(request)
+
+def file_upload_quota_broken(request):
+ """
+ You can't change handlers after reading FILES; this view shouldn't work.
+ """
+ response = file_upload_echo(request)
+ request.upload_handlers.insert(0, QuotaUploadHandler())
+ return response
+
+def file_upload_getlist_count(request):
+ """
+ Check the .getlist() function to ensure we receive the correct number of files.
+ """
+ file_counts = {}
+
+ for key in request.FILES.keys():
+ file_counts[key] = len(request.FILES.getlist(key))
+ return HttpResponse(simplejson.dumps(file_counts))
+
+def file_upload_errors(request):
+ request.upload_handlers.insert(0, ErroringUploadHandler())
+ return file_upload_echo(request)
diff --git a/parts/django/tests/regressiontests/fixtures_regress/__init__.py b/parts/django/tests/regressiontests/fixtures_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/absolute.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/absolute.json
new file mode 100644
index 0000000..37ed3f6
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/absolute.json
@@ -0,0 +1,9 @@
+[
+ {
+ "pk": "1",
+ "model": "fixtures_regress.absolute",
+ "fields": {
+ "name": "Load Absolute Path Test"
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/animal.xml b/parts/django/tests/regressiontests/fixtures_regress/fixtures/animal.xml
new file mode 100644
index 0000000..0383c60
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/animal.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="10" model="fixtures_regress.animal">
+ <field type="CharField" name="name">Emu</field>
+ <field type="CharField" name="latin_name">Dromaius novaehollandiae</field>
+ <field type="IntegerField" name="count">42</field>
+ <field type="FloatField" name="weight">1.2</field>
+ </object>
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn b/parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn
new file mode 100644
index 0000000..a8b0a0c
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn
@@ -0,0 +1 @@
+This data shouldn't load, as it's of an unknown file format. \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml b/parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml
new file mode 100644
index 0000000..87b809f
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objcts version="1.0">
+ <objct pk="2" model="fixtures.article">
+ <field type="CharField" name="headline">Poker on TV is great!</field>
+ <field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field>
+ </objct>
+</django-objcts> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json
new file mode 100644
index 0000000..e655fbb
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json
@@ -0,0 +1,83 @@
+[
+ {
+ "pk": 6,
+ "model": "fixtures_regress.channel",
+ "fields": {
+ "name": "Business"
+ }
+ },
+
+ {
+ "pk": 1,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 1",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 2,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 2",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 3,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 3",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 4,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 4",
+ "channels": [6]
+ }
+ },
+
+ {
+ "pk": 5,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 5",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 6,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 6",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 7,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 7",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 8,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 8",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 9,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Yet Another Article",
+ "channels": [6]
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/empty.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/empty.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/empty.json
@@ -0,0 +1 @@
+[] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json
new file mode 100644
index 0000000..fe50c65
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json
@@ -0,0 +1,32 @@
+[
+ {
+ "pk": "4",
+ "model": "fixtures_regress.person",
+ "fields": {
+ "name": "Neal Stephenson"
+ }
+ },
+ {
+ "pk": "2",
+ "model": "fixtures_regress.store",
+ "fields": {
+ "name": "Amazon"
+ }
+ },
+ {
+ "pk": "3",
+ "model": "fixtures_regress.store",
+ "fields": {
+ "name": "Borders"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "fixtures_regress.book",
+ "fields": {
+ "name": "Cryptonomicon",
+ "author": ["Neal Stephenson"],
+ "stores": [["Amazon"], ["Borders"]]
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json
new file mode 100644
index 0000000..00c482b
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json
@@ -0,0 +1,4 @@
+[
+ {"pk": 1, "model": "fixtures_regress.parent", "fields": {"name": "fred"}},
+ {"pk": 1, "model": "fixtures_regress.child", "fields": {"data": "apple"}}
+]
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json
new file mode 100644
index 0000000..08e5d4f
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json
@@ -0,0 +1,18 @@
+[
+ {
+ "pk": 1,
+ "model": "fixtures_regress.nkchild",
+ "fields": {
+ "data": "apple"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "fixtures_regress.reftonkchild",
+ "fields": {
+ "text": "my text",
+ "nk_fk" : ["apple"],
+ "nk_m2m": [["apple"]]
+ }
+ }
+]
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml b/parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml
new file mode 100644
index 0000000..7eb17a6
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="2" model="fixtures_regress.parent">
+ <field type="CharField" name="name">james</field>
+ </object>
+ <object pk="2" model="fixtures_regress.nkchild">
+ <field type="CharField" name="data">banana</field>
+ </object>
+ <object pk="2" model="fixtures_regress.reftonkchild">
+ <field type="CharField" name="text">other text</field>
+ <field to="fixtures_regress.nkchild" name="nk_fk" rel="ManyToOneRel">
+ <natural>apple</natural>
+ </field>
+ <field to="fixtures_regress.nkchild" name="nk_m2m" rel="ManyToManyRel">
+ <object>
+ <natural>banana</natural>
+ </object>
+ <object>
+ <natural>apple</natural>
+ </object>
+ </field>
+ </object>
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json
new file mode 100644
index 0000000..4bce792
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json
@@ -0,0 +1,25 @@
+[
+ {
+ "pk": 12,
+ "model": "fixtures_regress.person",
+ "fields": {
+ "name": "Greg Egan"
+ }
+ },
+ {
+ "pk": 11,
+ "model": "fixtures_regress.store",
+ "fields": {
+ "name": "Angus and Robertson"
+ }
+ },
+ {
+ "pk": 10,
+ "model": "fixtures_regress.book",
+ "fields": {
+ "name": "Permutation City",
+ "author": 12,
+ "stores": [11]
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml b/parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml
new file mode 100644
index 0000000..280ad37
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="22" model="fixtures_regress.person">
+ <field type="CharField" name="name">Orson Scott Card</field>
+ </object>
+ <object pk="21" model="fixtures_regress.store">
+ <field type="CharField" name="name">Collins Bookstore</field>
+ </object>
+ <object pk="20" model="fixtures_regress.book">
+ <field type="CharField" name="name">Ender's Game</field>
+ <field to="fixtures_regress.person" name="author" rel="ManyToOneRel">22</field>
+ <field to="fixtures_regress.store" name="stores" rel="ManyToManyRel">
+ <object pk="21"/>
+ </field>
+ </object>
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/pretty.xml b/parts/django/tests/regressiontests/fixtures_regress/fixtures/pretty.xml
new file mode 100644
index 0000000..68e5710
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/pretty.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="fixtures_regress.stuff">
+ <field type="CharField" name="name">
+ <None/>
+ </field>
+ <field to="auth.user" name="owner" rel="ManyToOneRel">
+ <None/>
+ </field>
+ </object>
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/sequence.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/sequence.json
new file mode 100644
index 0000000..60bfcdd
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/sequence.json
@@ -0,0 +1,12 @@
+[
+ {
+ "pk": "1",
+ "model": "fixtures_regress.animal",
+ "fields": {
+ "name": "Lion",
+ "latin_name": "Panthera leo",
+ "count": 3,
+ "weight": 1.2
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/fixtures_regress/fixtures/thingy.json b/parts/django/tests/regressiontests/fixtures_regress/fixtures/thingy.json
new file mode 100644
index 0000000..1693177
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/fixtures/thingy.json
@@ -0,0 +1,9 @@
+[
+ {
+ "pk": "1",
+ "model": "fixtures_regress.thingy",
+ "fields": {
+ "name": "Whatchamacallit"
+ }
+ }
+]
diff --git a/parts/django/tests/regressiontests/fixtures_regress/models.py b/parts/django/tests/regressiontests/fixtures_regress/models.py
new file mode 100644
index 0000000..7465fd2
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/models.py
@@ -0,0 +1,225 @@
+from django.db import models, DEFAULT_DB_ALIAS
+from django.contrib.auth.models import User
+from django.conf import settings
+
+
+class Animal(models.Model):
+ name = models.CharField(max_length=150)
+ latin_name = models.CharField(max_length=150)
+ count = models.IntegerField()
+ weight = models.FloatField()
+
+ # use a non-default name for the default manager
+ specimens = models.Manager()
+
+ def __unicode__(self):
+ return self.name
+
+
+class Plant(models.Model):
+ name = models.CharField(max_length=150)
+
+ class Meta:
+ # For testing when upper case letter in app name; regression for #4057
+ db_table = "Fixtures_regress_plant"
+
+class Stuff(models.Model):
+ name = models.CharField(max_length=20, null=True)
+ owner = models.ForeignKey(User, null=True)
+
+ def __unicode__(self):
+ return unicode(self.name) + u' is owned by ' + unicode(self.owner)
+
+
+class Absolute(models.Model):
+ name = models.CharField(max_length=40)
+
+ load_count = 0
+
+ def __init__(self, *args, **kwargs):
+ super(Absolute, self).__init__(*args, **kwargs)
+ Absolute.load_count += 1
+
+
+class Parent(models.Model):
+ name = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ('id',)
+
+
+class Child(Parent):
+ data = models.CharField(max_length=10)
+
+
+# Models to regression test #7572
+class Channel(models.Model):
+ name = models.CharField(max_length=255)
+
+
+class Article(models.Model):
+ title = models.CharField(max_length=255)
+ channels = models.ManyToManyField(Channel)
+
+ class Meta:
+ ordering = ('id',)
+
+
+# Models to regression test #11428
+class Widget(models.Model):
+ name = models.CharField(max_length=255)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+
+class WidgetProxy(Widget):
+ class Meta:
+ proxy = True
+
+
+# Check for forward references in FKs and M2Ms with natural keys
+class TestManager(models.Manager):
+ def get_by_natural_key(self, key):
+ return self.get(name=key)
+
+
+class Store(models.Model):
+ objects = TestManager()
+ name = models.CharField(max_length=255)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+ def natural_key(self):
+ return (self.name,)
+
+
+class Person(models.Model):
+ objects = TestManager()
+ name = models.CharField(max_length=255)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+ # Person doesn't actually have a dependency on store, but we need to define
+ # one to test the behaviour of the dependency resolution algorithm.
+ def natural_key(self):
+ return (self.name,)
+ natural_key.dependencies = ['fixtures_regress.store']
+
+
+class Book(models.Model):
+ name = models.CharField(max_length=255)
+ author = models.ForeignKey(Person)
+ stores = models.ManyToManyField(Store)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return u'%s by %s (available at %s)' % (
+ self.name,
+ self.author.name,
+ ', '.join(s.name for s in self.stores.all())
+ )
+
+
+class NKManager(models.Manager):
+ def get_by_natural_key(self, data):
+ return self.get(data=data)
+
+
+class NKChild(Parent):
+ data = models.CharField(max_length=10, unique=True)
+ objects = NKManager()
+
+ def natural_key(self):
+ return self.data
+
+ def __unicode__(self):
+ return u'NKChild %s:%s' % (self.name, self.data)
+
+
+class RefToNKChild(models.Model):
+ text = models.CharField(max_length=10)
+ nk_fk = models.ForeignKey(NKChild, related_name='ref_fks')
+ nk_m2m = models.ManyToManyField(NKChild, related_name='ref_m2ms')
+
+ def __unicode__(self):
+ return u'%s: Reference to %s [%s]' % (
+ self.text,
+ self.nk_fk,
+ ', '.join(str(o) for o in self.nk_m2m.all())
+ )
+
+
+# ome models with pathological circular dependencies
+class Circle1(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.circle2']
+
+
+class Circle2(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.circle1']
+
+
+class Circle3(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.circle3']
+
+
+class Circle4(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.circle5']
+
+
+class Circle5(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.circle6']
+
+
+class Circle6(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.circle4']
+
+
+class ExternalDependency(models.Model):
+ name = models.CharField(max_length=255)
+
+ def natural_key(self):
+ return self.name
+ natural_key.dependencies = ['fixtures_regress.book']
+
+
+# Model for regression test of #11101
+class Thingy(models.Model):
+ name = models.CharField(max_length=255)
diff --git a/parts/django/tests/regressiontests/fixtures_regress/tests.py b/parts/django/tests/regressiontests/fixtures_regress/tests.py
new file mode 100644
index 0000000..57ee7c0
--- /dev/null
+++ b/parts/django/tests/regressiontests/fixtures_regress/tests.py
@@ -0,0 +1,611 @@
+# -*- coding: utf-8 -*-
+# Unittests for fixtures.
+import os
+import sys
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from django.conf import settings
+from django.core import management
+from django.core.management.commands.dumpdata import sort_dependencies
+from django.core.management.base import CommandError
+from django.db.models import signals
+from django.db import DEFAULT_DB_ALIAS, transaction
+from django.test import TestCase, TransactionTestCase
+
+from models import Animal, Stuff
+from models import Absolute, Parent, Child
+from models import Article, Widget
+from models import Store, Person, Book
+from models import NKChild, RefToNKChild
+from models import Circle1, Circle2, Circle3
+from models import ExternalDependency
+from models import Thingy
+
+
+pre_save_checks = []
+def animal_pre_save_check(signal, sender, instance, **kwargs):
+ "A signal that is used to check the type of data loaded from fixtures"
+ pre_save_checks.append(
+ (
+ 'Count = %s (%s)' % (instance.count, type(instance.count)),
+ 'Weight = %s (%s)' % (instance.weight, type(instance.weight)),
+ )
+ )
+
+
+class TestFixtures(TestCase):
+ def test_duplicate_pk(self):
+ """
+ This is a regression test for ticket #3790.
+ """
+ # Load a fixture that uses PK=1
+ management.call_command(
+ 'loaddata',
+ 'sequence',
+ verbosity=0,
+ commit=False
+ )
+
+ # Create a new animal. Without a sequence reset, this new object
+ # will take a PK of 1 (on Postgres), and the save will fail.
+
+ animal = Animal(
+ name='Platypus',
+ latin_name='Ornithorhynchus anatinus',
+ count=2,
+ weight=2.2
+ )
+ animal.save()
+ self.assertTrue(animal.id > 1)
+
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle':
+ def test_pretty_print_xml(self):
+ """
+ Regression test for ticket #4558 -- pretty printing of XML fixtures
+ doesn't affect parsing of None values.
+ """
+ # Load a pretty-printed XML fixture with Nulls.
+ management.call_command(
+ 'loaddata',
+ 'pretty.xml',
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(Stuff.objects.all()[0].name, None)
+ self.assertEqual(Stuff.objects.all()[0].owner, None)
+
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+ def test_pretty_print_xml_empty_strings(self):
+ """
+ Regression test for ticket #4558 -- pretty printing of XML fixtures
+ doesn't affect parsing of None values.
+ """
+ # Load a pretty-printed XML fixture with Nulls.
+ management.call_command(
+ 'loaddata',
+ 'pretty.xml',
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(Stuff.objects.all()[0].name, u'')
+ self.assertEqual(Stuff.objects.all()[0].owner, None)
+
+ def test_absolute_path(self):
+ """
+ Regression test for ticket #6436 --
+ os.path.join will throw away the initial parts of a path if it
+ encounters an absolute path.
+ This means that if a fixture is specified as an absolute path,
+ we need to make sure we don't discover the absolute path in every
+ fixture directory.
+ """
+ load_absolute_path = os.path.join(
+ os.path.dirname(__file__),
+ 'fixtures',
+ 'absolute.json'
+ )
+ management.call_command(
+ 'loaddata',
+ load_absolute_path,
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(Absolute.load_count, 1)
+
+
+ def test_unknown_format(self):
+ """
+ Test for ticket #4371 -- Loading data of an unknown format should fail
+ Validate that error conditions are caught correctly
+ """
+ stderr = StringIO()
+ management.call_command(
+ 'loaddata',
+ 'bad_fixture1.unkn',
+ verbosity=0,
+ commit=False,
+ stderr=stderr,
+ )
+ self.assertEqual(
+ stderr.getvalue(),
+ "Problem installing fixture 'bad_fixture1': unkn is not a known serialization format.\n"
+ )
+
+ def test_invalid_data(self):
+ """
+ Test for ticket #4371 -- Loading a fixture file with invalid data
+ using explicit filename.
+ Validate that error conditions are caught correctly
+ """
+ stderr = StringIO()
+ management.call_command(
+ 'loaddata',
+ 'bad_fixture2.xml',
+ verbosity=0,
+ commit=False,
+ stderr=stderr,
+ )
+ self.assertEqual(
+ stderr.getvalue(),
+ "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n"
+ )
+
+ def test_invalid_data_no_ext(self):
+ """
+ Test for ticket #4371 -- Loading a fixture file with invalid data
+ without file extension.
+ Validate that error conditions are caught correctly
+ """
+ stderr = StringIO()
+ management.call_command(
+ 'loaddata',
+ 'bad_fixture2',
+ verbosity=0,
+ commit=False,
+ stderr=stderr,
+ )
+ self.assertEqual(
+ stderr.getvalue(),
+ "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n"
+ )
+
+ def test_empty(self):
+ """
+ Test for ticket #4371 -- Loading a fixture file with no data returns an error.
+ Validate that error conditions are caught correctly
+ """
+ stderr = StringIO()
+ management.call_command(
+ 'loaddata',
+ 'empty',
+ verbosity=0,
+ commit=False,
+ stderr=stderr,
+ )
+ self.assertEqual(
+ stderr.getvalue(),
+ "No fixture data found for 'empty'. (File format may be invalid.)\n"
+ )
+
+ def test_abort_loaddata_on_error(self):
+ """
+ Test for ticket #4371 -- If any of the fixtures contain an error,
+ loading is aborted.
+ Validate that error conditions are caught correctly
+ """
+ stderr = StringIO()
+ management.call_command(
+ 'loaddata',
+ 'empty',
+ verbosity=0,
+ commit=False,
+ stderr=stderr,
+ )
+ self.assertEqual(
+ stderr.getvalue(),
+ "No fixture data found for 'empty'. (File format may be invalid.)\n"
+ )
+
+ def test_error_message(self):
+ """
+ (Regression for #9011 - error message is correct)
+ """
+ stderr = StringIO()
+ management.call_command(
+ 'loaddata',
+ 'bad_fixture2',
+ 'animal',
+ verbosity=0,
+ commit=False,
+ stderr=stderr,
+ )
+ self.assertEqual(
+ stderr.getvalue(),
+ "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n"
+ )
+
+ def test_pg_sequence_resetting_checks(self):
+ """
+ Test for ticket #7565 -- PostgreSQL sequence resetting checks shouldn't
+ ascend to parent models when inheritance is used
+ (since they are treated individually).
+ """
+ management.call_command(
+ 'loaddata',
+ 'model-inheritance.json',
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(Parent.objects.all()[0].id, 1)
+ self.assertEqual(Child.objects.all()[0].id, 1)
+
+ def test_close_connection_after_loaddata(self):
+ """
+ Test for ticket #7572 -- MySQL has a problem if the same connection is
+ used to create tables, load data, and then query over that data.
+ To compensate, we close the connection after running loaddata.
+ This ensures that a new connection is opened when test queries are
+ issued.
+ """
+ management.call_command(
+ 'loaddata',
+ 'big-fixture.json',
+ verbosity=0,
+ commit=False
+ )
+ articles = Article.objects.exclude(id=9)
+ self.assertEqual(
+ list(articles.values_list('id', flat=True)),
+ [1, 2, 3, 4, 5, 6, 7, 8]
+ )
+ # Just for good measure, run the same query again.
+ # Under the influence of ticket #7572, this will
+ # give a different result to the previous call.
+ self.assertEqual(
+ list(articles.values_list('id', flat=True)),
+ [1, 2, 3, 4, 5, 6, 7, 8]
+ )
+
+ def test_field_value_coerce(self):
+ """
+ Test for tickets #8298, #9942 - Field values should be coerced into the
+ correct type by the deserializer, not as part of the database write.
+ """
+ global pre_save_checks
+ pre_save_checks = []
+ signals.pre_save.connect(animal_pre_save_check)
+ management.call_command(
+ 'loaddata',
+ 'animal.xml',
+ verbosity=0,
+ commit=False,
+ )
+ self.assertEqual(
+ pre_save_checks,
+ [
+ ("Count = 42 (<type 'int'>)", "Weight = 1.2 (<type 'float'>)")
+ ]
+ )
+ signals.pre_save.disconnect(animal_pre_save_check)
+
+ def test_dumpdata_uses_default_manager(self):
+ """
+ Regression for #11286
+ Ensure that dumpdata honors the default manager
+ Dump the current contents of the database as a JSON fixture
+ """
+ management.call_command(
+ 'loaddata',
+ 'animal.xml',
+ verbosity=0,
+ commit=False,
+ )
+ management.call_command(
+ 'loaddata',
+ 'sequence.json',
+ verbosity=0,
+ commit=False,
+ )
+ animal = Animal(
+ name='Platypus',
+ latin_name='Ornithorhynchus anatinus',
+ count=2,
+ weight=2.2
+ )
+ animal.save()
+
+ stdout = StringIO()
+ management.call_command(
+ 'dumpdata',
+ 'fixtures_regress.animal',
+ format='json',
+ stdout=stdout
+ )
+
+ # Output order isn't guaranteed, so check for parts
+ data = stdout.getvalue()
+ lion_json = '{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}'
+ emu_json = '{"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}'
+ platypus_json = '{"pk": %d, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2000000000000002, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}'
+ platypus_json = platypus_json % animal.pk
+
+ self.assertEqual(len(data), len('[%s]' % ', '.join([lion_json, emu_json, platypus_json])))
+ self.assertTrue(lion_json in data)
+ self.assertTrue(emu_json in data)
+ self.assertTrue(platypus_json in data)
+
+ def test_proxy_model_included(self):
+ """
+ Regression for #11428 - Proxy models aren't included when you dumpdata
+ """
+ stdout = StringIO()
+ # Create an instance of the concrete class
+ Widget(name='grommet').save()
+ management.call_command(
+ 'dumpdata',
+ 'fixtures_regress.widget',
+ 'fixtures_regress.widgetproxy',
+ format='json',
+ stdout=stdout
+ )
+ self.assertEqual(
+ stdout.getvalue(),
+ """[{"pk": 1, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]"""
+ )
+
+
+class NaturalKeyFixtureTests(TestCase):
+ def assertRaisesMessage(self, exc, msg, func, *args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except Exception, e:
+ self.assertEqual(msg, str(e))
+ self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e)))
+
+ def test_nk_deserialize(self):
+ """
+ Test for ticket #13030 - Python based parser version
+ natural keys deserialize with fk to inheriting model
+ """
+ management.call_command(
+ 'loaddata',
+ 'model-inheritance.json',
+ verbosity=0,
+ commit=False
+ )
+ management.call_command(
+ 'loaddata',
+ 'nk-inheritance.json',
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(
+ NKChild.objects.get(pk=1).data,
+ 'apple'
+ )
+
+ self.assertEqual(
+ RefToNKChild.objects.get(pk=1).nk_fk.data,
+ 'apple'
+ )
+
+ def test_nk_deserialize_xml(self):
+ """
+ Test for ticket #13030 - XML version
+ natural keys deserialize with fk to inheriting model
+ """
+ management.call_command(
+ 'loaddata',
+ 'model-inheritance.json',
+ verbosity=0,
+ commit=False
+ )
+ management.call_command(
+ 'loaddata',
+ 'nk-inheritance.json',
+ verbosity=0,
+ commit=False
+ )
+ management.call_command(
+ 'loaddata',
+ 'nk-inheritance2.xml',
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(
+ NKChild.objects.get(pk=2).data,
+ 'banana'
+ )
+ self.assertEqual(
+ RefToNKChild.objects.get(pk=2).nk_fk.data,
+ 'apple'
+ )
+
+ def test_nk_on_serialize(self):
+ """
+ Check that natural key requirements are taken into account
+ when serializing models
+ """
+ management.call_command(
+ 'loaddata',
+ 'forward_ref_lookup.json',
+ verbosity=0,
+ commit=False
+ )
+
+ stdout = StringIO()
+ management.call_command(
+ 'dumpdata',
+ 'fixtures_regress.book',
+ 'fixtures_regress.person',
+ 'fixtures_regress.store',
+ verbosity=0,
+ format='json',
+ use_natural_keys=True,
+ stdout=stdout,
+ )
+ self.assertEqual(
+ stdout.getvalue(),
+ """[{"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]"""
+ )
+
+ def test_dependency_sorting(self):
+ """
+ Now lets check the dependency sorting explicitly
+ It doesn't matter what order you mention the models
+ Store *must* be serialized before then Person, and both
+ must be serialized before Book.
+ """
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Book, Person, Store])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Store, Person, Book]
+ )
+
+ def test_dependency_sorting_2(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Book, Store, Person])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Store, Person, Book]
+ )
+
+ def test_dependency_sorting_3(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Store, Book, Person])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Store, Person, Book]
+ )
+
+ def test_dependency_sorting_4(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Store, Person, Book])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Store, Person, Book]
+ )
+
+ def test_dependency_sorting_5(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Person, Book, Store])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Store, Person, Book]
+ )
+
+ def test_dependency_sorting_6(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Person, Store, Book])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Store, Person, Book]
+ )
+
+ def test_dependency_sorting_dangling(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Person, Circle1, Store, Book])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Circle1, Store, Person, Book]
+ )
+
+ def test_dependency_sorting_tight_circular(self):
+ self.assertRaisesMessage(
+ CommandError,
+ """Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.""",
+ sort_dependencies,
+ [('fixtures_regress', [Person, Circle2, Circle1, Store, Book])],
+ )
+
+ def test_dependency_sorting_tight_circular_2(self):
+ self.assertRaisesMessage(
+ CommandError,
+ """Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.""",
+ sort_dependencies,
+ [('fixtures_regress', [Circle1, Book, Circle2])],
+ )
+
+ def test_dependency_self_referential(self):
+ self.assertRaisesMessage(
+ CommandError,
+ """Can't resolve dependencies for fixtures_regress.Circle3 in serialized app list.""",
+ sort_dependencies,
+ [('fixtures_regress', [Book, Circle3])],
+ )
+
+ def test_dependency_sorting_long(self):
+ self.assertRaisesMessage(
+ CommandError,
+ """Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2, fixtures_regress.Circle3 in serialized app list.""",
+ sort_dependencies,
+ [('fixtures_regress', [Person, Circle2, Circle1, Circle3, Store, Book])],
+ )
+
+ def test_dependency_sorting_normal(self):
+ sorted_deps = sort_dependencies(
+ [('fixtures_regress', [Person, ExternalDependency, Book])]
+ )
+ self.assertEqual(
+ sorted_deps,
+ [Person, Book, ExternalDependency]
+ )
+
+ def test_normal_pk(self):
+ """
+ Check that normal primary keys still work
+ on a model with natural key capabilities
+ """
+ management.call_command(
+ 'loaddata',
+ 'non_natural_1.json',
+ verbosity=0,
+ commit=False
+ )
+ management.call_command(
+ 'loaddata',
+ 'forward_ref_lookup.json',
+ verbosity=0,
+ commit=False
+ )
+ management.call_command(
+ 'loaddata',
+ 'non_natural_2.xml',
+ verbosity=0,
+ commit=False
+ )
+ books = Book.objects.all()
+ self.assertEqual(
+ books.__repr__(),
+ """[<Book: Cryptonomicon by Neal Stephenson (available at Amazon, Borders)>, <Book: Ender's Game by Orson Scott Card (available at Collins Bookstore)>, <Book: Permutation City by Greg Egan (available at Angus and Robertson)>]"""
+ )
+
+
+class TestTicket11101(TransactionTestCase):
+
+ def ticket_11101(self):
+ management.call_command(
+ 'loaddata',
+ 'thingy.json',
+ verbosity=0,
+ commit=False
+ )
+ self.assertEqual(Thingy.objects.count(), 1)
+ transaction.rollback()
+ self.assertEqual(Thingy.objects.count(), 0)
+
+ def test_ticket_11101(self):
+ """Test that fixtures can be rolled back (ticket #11101)."""
+ ticket_11101 = transaction.commit_manually(self.ticket_11101)
+ ticket_11101()
diff --git a/parts/django/tests/regressiontests/forms/__init__.py b/parts/django/tests/regressiontests/forms/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/__init__.py
diff --git a/parts/django/tests/regressiontests/forms/localflavor/__init__.py b/parts/django/tests/regressiontests/forms/localflavor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/__init__.py
diff --git a/parts/django/tests/regressiontests/forms/localflavor/ar.py b/parts/django/tests/regressiontests/forms/localflavor/ar.py
new file mode 100644
index 0000000..9c67269
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/ar.py
@@ -0,0 +1,99 @@
+from django.contrib.localflavor.ar.forms import (ARProvinceSelect,
+ ARPostalCodeField, ARDNIField, ARCUITField)
+
+from utils import LocalFlavorTestCase
+
+
+class ARLocalFlavorTests(LocalFlavorTestCase):
+ def test_ARProvinceSelect(self):
+ f = ARProvinceSelect()
+ out = u'''<select name="provincias">
+<option value="B">Buenos Aires</option>
+<option value="K">Catamarca</option>
+<option value="H">Chaco</option>
+<option value="U">Chubut</option>
+<option value="C">Ciudad Aut\xf3noma de Buenos Aires</option>
+<option value="X">C\xf3rdoba</option>
+<option value="W">Corrientes</option>
+<option value="E">Entre R\xedos</option>
+<option value="P">Formosa</option>
+<option value="Y">Jujuy</option>
+<option value="L">La Pampa</option>
+<option value="F">La Rioja</option>
+<option value="M">Mendoza</option>
+<option value="N">Misiones</option>
+<option value="Q">Neuqu\xe9n</option>
+<option value="R">R\xedo Negro</option>
+<option value="A" selected="selected">Salta</option>
+<option value="J">San Juan</option>
+<option value="D">San Luis</option>
+<option value="Z">Santa Cruz</option>
+<option value="S">Santa Fe</option>
+<option value="G">Santiago del Estero</option>
+<option value="V">Tierra del Fuego, Ant\xe1rtida e Islas del Atl\xe1ntico Sur</option>
+<option value="T">Tucum\xe1n</option>
+</select>'''
+ self.assertEqual(f.render('provincias', 'A'), out)
+
+ def test_ARPostalCodeField(self):
+ error_format = [u'Enter a postal code in the format NNNN or ANNNNAAA.']
+ error_atmost = [u'Ensure this value has at most 8 characters (it has 9).']
+ error_atleast = [u'Ensure this value has at least 4 characters (it has 3).']
+ valid = {
+ '5000': '5000',
+ 'C1064AAB': 'C1064AAB',
+ 'c1064AAB': 'C1064AAB',
+ 'C1064aab': 'C1064AAB',
+ '4400': '4400',
+ u'C1064AAB': 'C1064AAB',
+ }
+ invalid = {
+ 'C1064AABB': error_atmost + error_format,
+ 'C1064AA': error_format,
+ 'C1064AB': error_format,
+ '106AAB': error_format,
+ '500': error_atleast + error_format,
+ '5PPP': error_format,
+ }
+ self.assertFieldOutput(ARPostalCodeField, valid, invalid)
+
+ def test_ARDNIField(self):
+ error_length = [u'This field requires 7 or 8 digits.']
+ error_digitsonly = [u'This field requires only numbers.']
+ valid = {
+ '20123456': '20123456',
+ '20.123.456': '20123456',
+ u'20123456': '20123456',
+ u'20.123.456': '20123456',
+ '20.123456': '20123456',
+ '9123456': '9123456',
+ '9.123.456': '9123456',
+ }
+ invalid = {
+ '101234566': error_length,
+ 'W0123456': error_digitsonly,
+ '10,123,456': error_digitsonly,
+ }
+ self.assertFieldOutput(ARDNIField, valid, invalid)
+
+ def test_ARCUITField(self):
+ error_format = [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.']
+ error_invalid = [u'Invalid CUIT.']
+ valid = {
+ '20-10123456-9': '20-10123456-9',
+ u'20-10123456-9': '20-10123456-9',
+ '27-10345678-4': '27-10345678-4',
+ '20101234569': '20-10123456-9',
+ '27103456784': '27-10345678-4',
+ }
+ invalid = {
+ '2-10123456-9': error_format,
+ '210123456-9': error_format,
+ '20-10123456': error_format,
+ '20-10123456-': error_format,
+ '20-10123456-5': error_invalid,
+ '2-10123456-9': error_format,
+ '27-10345678-1': error_invalid,
+ u'27-10345678-1': error_invalid,
+ }
+ self.assertFieldOutput(ARCUITField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/at.py b/parts/django/tests/regressiontests/forms/localflavor/at.py
new file mode 100644
index 0000000..3fa50ac
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/at.py
@@ -0,0 +1,45 @@
+from django.contrib.localflavor.at.forms import (ATZipCodeField, ATStateSelect,
+ ATSocialSecurityNumberField)
+
+from utils import LocalFlavorTestCase
+
+
+class ATLocalFlavorTests(LocalFlavorTestCase):
+ def test_ATStateSelect(self):
+ f = ATStateSelect()
+ out = u'''<select name="bundesland">
+<option value="BL">Burgenland</option>
+<option value="KA">Carinthia</option>
+<option value="NO">Lower Austria</option>
+<option value="OO">Upper Austria</option>
+<option value="SA">Salzburg</option>
+<option value="ST">Styria</option>
+<option value="TI">Tyrol</option>
+<option value="VO">Vorarlberg</option>
+<option value="WI" selected="selected">Vienna</option>
+</select>'''
+ self.assertEqual(f.render('bundesland', 'WI'), out)
+
+ def test_ATZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXX.']
+ valid = {
+ '1150': '1150',
+ '4020': '4020',
+ '8020': '8020',
+ }
+ invalid = {
+ '111222': error_format,
+ 'eeffee': error_format,
+ }
+ self.assertFieldOutput(ATZipCodeField, valid, invalid)
+
+ def test_ATSocialSecurityNumberField(self):
+ error_format = [u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.']
+ valid = {
+ '1237 010180': '1237 010180',
+ }
+ invalid = {
+ '1237 010181': error_format,
+ '12370 010180': error_format,
+ }
+ self.assertFieldOutput(ATSocialSecurityNumberField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/au.py b/parts/django/tests/regressiontests/forms/localflavor/au.py
new file mode 100644
index 0000000..4bd8a76
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/au.py
@@ -0,0 +1,50 @@
+from django.contrib.localflavor.au.forms import (AUPostCodeField,
+ AUPhoneNumberField, AUStateSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class AULocalFlavorTests(LocalFlavorTestCase):
+ def test_AUStateSelect(self):
+ f = AUStateSelect()
+ out = u'''<select name="state">
+<option value="ACT">Australian Capital Territory</option>
+<option value="NSW" selected="selected">New South Wales</option>
+<option value="NT">Northern Territory</option>
+<option value="QLD">Queensland</option>
+<option value="SA">South Australia</option>
+<option value="TAS">Tasmania</option>
+<option value="VIC">Victoria</option>
+<option value="WA">Western Australia</option>
+</select>'''
+ self.assertEqual(f.render('state', 'NSW'), out)
+
+ def test_AUPostCodeField(self):
+ error_format = [u'Enter a 4 digit post code.']
+ valid = {
+ '1234': '1234',
+ '2000': '2000',
+ }
+ invalid = {
+ 'abcd': error_format,
+ '20001': error_format,
+ }
+ self.assertFieldOutput(AUPostCodeField, valid, invalid)
+
+ def test_AUPhoneNumberField(self):
+ error_format = [u'Phone numbers must contain 10 digits.']
+ valid = {
+ '1234567890': '1234567890',
+ '0213456789': '0213456789',
+ '02 13 45 67 89': '0213456789',
+ '(02) 1345 6789': '0213456789',
+ '(02) 1345-6789': '0213456789',
+ '(02)1345-6789': '0213456789',
+ '0408 123 456': '0408123456',
+ }
+ invalid = {
+ '123': error_format,
+ '1800DJANGO': error_format,
+ }
+ self.assertFieldOutput(AUPhoneNumberField, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/br.py b/parts/django/tests/regressiontests/forms/localflavor/br.py
new file mode 100644
index 0000000..6ae7105
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/br.py
@@ -0,0 +1,144 @@
+from django.contrib.localflavor.br.forms import (BRZipCodeField,
+ BRCNPJField, BRCPFField, BRPhoneNumberField, BRStateSelect,
+ BRStateChoiceField)
+
+from utils import LocalFlavorTestCase
+
+
+class BRLocalFlavorTests(LocalFlavorTestCase):
+ def test_BRZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXXX-XXX.']
+ valid = {
+ '12345-123': '12345-123',
+ }
+ invalid = {
+ '12345_123': error_format,
+ '1234-123': error_format,
+ 'abcde-abc': error_format,
+ '12345-': error_format,
+ '-123': error_format,
+ }
+ self.assertFieldOutput(BRZipCodeField, valid, invalid)
+
+ def test_BRCNPJField(self):
+ error_format = [u'Invalid CNPJ number.']
+ error_numbersonly = [u'This field requires only numbers.']
+ valid = {
+ '64.132.916/0001-88': '64.132.916/0001-88',
+ '64-132-916/0001-88': '64-132-916/0001-88',
+ '64132916/0001-88': '64132916/0001-88',
+ }
+ invalid = {
+ '12-345-678/9012-10': error_format,
+ '12.345.678/9012-10': error_format,
+ '12345678/9012-10': error_format,
+ '64.132.916/0001-XX': error_numbersonly,
+ }
+ self.assertFieldOutput(BRCNPJField, valid, invalid)
+
+ def test_BRCPFField(self):
+ error_format = [u'Invalid CPF number.']
+ error_numbersonly = [u'This field requires only numbers.']
+ error_atmost_chars = [u'Ensure this value has at most 14 characters (it has 15).']
+ error_atleast_chars = [u'Ensure this value has at least 11 characters (it has 10).']
+ error_atmost = [u'This field requires at most 11 digits or 14 characters.']
+ valid = {
+ '663.256.017-26': '663.256.017-26',
+ '66325601726': '66325601726',
+ '375.788.573-20': '375.788.573-20',
+ '84828509895': '84828509895',
+ }
+ invalid = {
+ '489.294.654-54': error_format,
+ '295.669.575-98': error_format,
+ '539.315.127-22': error_format,
+ '375.788.573-XX': error_numbersonly,
+ '375.788.573-000': error_atmost_chars,
+ '123.456.78': error_atleast_chars,
+ '123456789555': error_atmost,
+ }
+ self.assertFieldOutput(BRCPFField, valid, invalid)
+
+ def test_BRPhoneNumberField(self):
+ # TODO: this doesn't test for any invalid inputs.
+ valid = {
+ '41-3562-3464': u'41-3562-3464',
+ '4135623464': u'41-3562-3464',
+ '41 3562-3464': u'41-3562-3464',
+ '41 3562 3464': u'41-3562-3464',
+ '(41) 3562 3464': u'41-3562-3464',
+ '41.3562.3464': u'41-3562-3464',
+ '41.3562-3464': u'41-3562-3464',
+ ' (41) 3562.3464': u'41-3562-3464',
+ }
+ invalid = {}
+ self.assertFieldOutput(BRPhoneNumberField, valid, invalid)
+
+ def test_BRStateSelect(self):
+ f = BRStateSelect()
+ out = u'''<select name="states">
+<option value="AC">Acre</option>
+<option value="AL">Alagoas</option>
+<option value="AP">Amap\xe1</option>
+<option value="AM">Amazonas</option>
+<option value="BA">Bahia</option>
+<option value="CE">Cear\xe1</option>
+<option value="DF">Distrito Federal</option>
+<option value="ES">Esp\xedrito Santo</option>
+<option value="GO">Goi\xe1s</option>
+<option value="MA">Maranh\xe3o</option>
+<option value="MT">Mato Grosso</option>
+<option value="MS">Mato Grosso do Sul</option>
+<option value="MG">Minas Gerais</option>
+<option value="PA">Par\xe1</option>
+<option value="PB">Para\xedba</option>
+<option value="PR" selected="selected">Paran\xe1</option>
+<option value="PE">Pernambuco</option>
+<option value="PI">Piau\xed</option>
+<option value="RJ">Rio de Janeiro</option>
+<option value="RN">Rio Grande do Norte</option>
+<option value="RS">Rio Grande do Sul</option>
+<option value="RO">Rond\xf4nia</option>
+<option value="RR">Roraima</option>
+<option value="SC">Santa Catarina</option>
+<option value="SP">S\xe3o Paulo</option>
+<option value="SE">Sergipe</option>
+<option value="TO">Tocantins</option>
+</select>'''
+ self.assertEqual(f.render('states', 'PR'), out)
+
+ def test_BRStateChoiceField(self):
+ error_invalid = [u'Select a valid brazilian state. That state is not one of the available states.']
+ valid = {
+ 'AC': 'AC',
+ 'AL': 'AL',
+ 'AP': 'AP',
+ 'AM': 'AM',
+ 'BA': 'BA',
+ 'CE': 'CE',
+ 'DF': 'DF',
+ 'ES': 'ES',
+ 'GO': 'GO',
+ 'MA': 'MA',
+ 'MT': 'MT',
+ 'MS': 'MS',
+ 'MG': 'MG',
+ 'PA': 'PA',
+ 'PB': 'PB',
+ 'PR': 'PR',
+ 'PE': 'PE',
+ 'PI': 'PI',
+ 'RJ': 'RJ',
+ 'RN': 'RN',
+ 'RS': 'RS',
+ 'RO': 'RO',
+ 'RR': 'RR',
+ 'SC': 'SC',
+ 'SP': 'SP',
+ 'SE': 'SE',
+ 'TO': 'TO',
+ }
+ invalid = {
+ 'pr': error_invalid,
+ }
+ self.assertFieldOutput(BRStateChoiceField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/ca.py b/parts/django/tests/regressiontests/forms/localflavor/ca.py
new file mode 100644
index 0000000..575f41c
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/ca.py
@@ -0,0 +1,95 @@
+from django.contrib.localflavor.ca.forms import (CAPostalCodeField,
+ CAPhoneNumberField, CAProvinceField, CAProvinceSelect,
+ CASocialInsuranceNumberField)
+
+from utils import LocalFlavorTestCase
+
+
+class CALocalFlavorTests(LocalFlavorTestCase):
+ def test_CAProvinceSelect(self):
+ f = CAProvinceSelect()
+ out = u'''<select name="province">
+<option value="AB" selected="selected">Alberta</option>
+<option value="BC">British Columbia</option>
+<option value="MB">Manitoba</option>
+<option value="NB">New Brunswick</option>
+<option value="NF">Newfoundland and Labrador</option>
+<option value="NT">Northwest Territories</option>
+<option value="NS">Nova Scotia</option>
+<option value="NU">Nunavut</option>
+<option value="ON">Ontario</option>
+<option value="PE">Prince Edward Island</option>
+<option value="QC">Quebec</option>
+<option value="SK">Saskatchewan</option>
+<option value="YK">Yukon</option>
+</select>'''
+ self.assertEqual(f.render('province', 'AB'), out)
+
+ def test_CAPostalCodeField(self):
+ error_format = [u'Enter a postal code in the format XXX XXX.']
+ valid = {
+ 'T2S 2H7': 'T2S 2H7',
+ 'T2S 2W7': 'T2S 2W7',
+ 'T2S 2Z7': 'T2S 2Z7',
+ 'T2Z 2H7': 'T2Z 2H7',
+
+ }
+ invalid = {
+ 'T2S2H7' : error_format,
+ 'T2S 2H' : error_format,
+ '2T6 H8I': error_format,
+ 'T2S2H' : error_format,
+ 90210 : error_format,
+ 'W2S 2H3': error_format,
+ 'Z2S 2H3': error_format,
+ 'F2S 2H3': error_format,
+ 'A2S 2D3': error_format,
+ 'A2I 2R3': error_format,
+ 'A2Q 2R3': error_format,
+ 'U2B 2R3': error_format,
+ 'O2B 2R3': error_format,
+ }
+ self.assertFieldOutput(CAPostalCodeField, valid, invalid)
+
+ def test_CAPhoneNumberField(self):
+ error_format = [u'Phone numbers must be in XXX-XXX-XXXX format.']
+ valid = {
+ '403-555-1212': '403-555-1212',
+ '4035551212': '403-555-1212',
+ '403 555-1212': '403-555-1212',
+ '(403) 555-1212': '403-555-1212',
+ '403 555 1212': '403-555-1212',
+ '403.555.1212': '403-555-1212',
+ '403.555-1212': '403-555-1212',
+ ' (403) 555.1212 ': '403-555-1212',
+ }
+ invalid = {
+ '555-1212': error_format,
+ '403-55-1212': error_format,
+ }
+ self.assertFieldOutput(CAPhoneNumberField, valid, invalid)
+
+ def test_CAProvinceField(self):
+ error_format = [u'Enter a Canadian province or territory.']
+ valid = {
+ 'ab': 'AB',
+ 'BC': 'BC',
+ 'nova scotia': 'NS',
+ ' manitoba ': 'MB',
+ }
+ invalid = {
+ 'T2S 2H7': error_format,
+ }
+ self.assertFieldOutput(CAProvinceField, valid, invalid)
+
+ def test_CASocialInsuranceField(self):
+ error_format = [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.']
+ valid = {
+ '046-454-286': '046-454-286',
+ }
+ invalid = {
+ '046-454-287': error_format,
+ '046 454 286': error_format,
+ '046-44-286': error_format,
+ }
+ self.assertFieldOutput(CASocialInsuranceNumberField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/ch.py b/parts/django/tests/regressiontests/forms/localflavor/ch.py
new file mode 100644
index 0000000..c67bfcf
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/ch.py
@@ -0,0 +1,75 @@
+from django.contrib.localflavor.ch.forms import (CHZipCodeField,
+ CHPhoneNumberField, CHIdentityCardNumberField, CHStateSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class CHLocalFlavorTests(LocalFlavorTestCase):
+ def test_CHStateSelect(self):
+ f = CHStateSelect()
+ out = u'''<select name="state">
+<option value="AG" selected="selected">Aargau</option>
+<option value="AI">Appenzell Innerrhoden</option>
+<option value="AR">Appenzell Ausserrhoden</option>
+<option value="BS">Basel-Stadt</option>
+<option value="BL">Basel-Land</option>
+<option value="BE">Berne</option>
+<option value="FR">Fribourg</option>
+<option value="GE">Geneva</option>
+<option value="GL">Glarus</option>
+<option value="GR">Graubuenden</option>
+<option value="JU">Jura</option>
+<option value="LU">Lucerne</option>
+<option value="NE">Neuchatel</option>
+<option value="NW">Nidwalden</option>
+<option value="OW">Obwalden</option>
+<option value="SH">Schaffhausen</option>
+<option value="SZ">Schwyz</option>
+<option value="SO">Solothurn</option>
+<option value="SG">St. Gallen</option>
+<option value="TG">Thurgau</option>
+<option value="TI">Ticino</option>
+<option value="UR">Uri</option>
+<option value="VS">Valais</option>
+<option value="VD">Vaud</option>
+<option value="ZG">Zug</option>
+<option value="ZH">Zurich</option>
+</select>'''
+ self.assertEqual(f.render('state', 'AG'), out)
+
+ def test_CHZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXX.']
+ valid = {
+ '1234': '1234',
+ '0000': '0000',
+ }
+ invalid = {
+ '800x': error_format,
+ '80 00': error_format,
+ }
+ self.assertFieldOutput(CHZipCodeField, valid, invalid)
+
+ def test_CHPhoneNumberField(self):
+ error_format = [u'Phone numbers must be in 0XX XXX XX XX format.']
+ valid = {
+ '012 345 67 89': '012 345 67 89',
+ '0123456789': '012 345 67 89',
+ }
+ invalid = {
+ '01234567890': error_format,
+ '1234567890': error_format,
+ }
+ self.assertFieldOutput(CHPhoneNumberField, valid, invalid)
+
+ def test_CHIdentityCardNumberField(self):
+ error_format = [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
+ valid = {
+ 'C1234567<0': 'C1234567<0',
+ '2123456700': '2123456700',
+ }
+ invalid = {
+ 'C1234567<1': error_format,
+ '2123456701': error_format,
+ }
+ self.assertFieldOutput(CHIdentityCardNumberField, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/cl.py b/parts/django/tests/regressiontests/forms/localflavor/cl.py
new file mode 100644
index 0000000..15b8c7b
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/cl.py
@@ -0,0 +1,56 @@
+from django.contrib.localflavor.cl.forms import CLRutField, CLRegionSelect
+from django.core.exceptions import ValidationError
+
+from utils import LocalFlavorTestCase
+
+
+class CLLocalFlavorTests(LocalFlavorTestCase):
+ def test_CLRegionSelect(self):
+ f = CLRegionSelect()
+ out = u'''<select name="foo">
+<option value="RM">Regi\xf3n Metropolitana de Santiago</option>
+<option value="I">Regi\xf3n de Tarapac\xe1</option>
+<option value="II">Regi\xf3n de Antofagasta</option>
+<option value="III">Regi\xf3n de Atacama</option>
+<option value="IV">Regi\xf3n de Coquimbo</option>
+<option value="V">Regi\xf3n de Valpara\xedso</option>
+<option value="VI">Regi\xf3n del Libertador Bernardo O&#39;Higgins</option>
+<option value="VII">Regi\xf3n del Maule</option>
+<option value="VIII">Regi\xf3n del B\xedo B\xedo</option>
+<option value="IX">Regi\xf3n de la Araucan\xeda</option>
+<option value="X">Regi\xf3n de los Lagos</option>
+<option value="XI">Regi\xf3n de Ays\xe9n del General Carlos Ib\xe1\xf1ez del Campo</option>
+<option value="XII">Regi\xf3n de Magallanes y la Ant\xe1rtica Chilena</option>
+<option value="XIV">Regi\xf3n de Los R\xedos</option>
+<option value="XV">Regi\xf3n de Arica-Parinacota</option>
+</select>'''
+ self.assertEqual(f.render('foo', 'bar'), out)
+
+ def test_CLRutField(self):
+ error_invalid = [u'The Chilean RUT is not valid.']
+ error_format = [u'Enter a valid Chilean RUT. The format is XX.XXX.XXX-X.']
+ valid = {
+ '11-6': '11-6',
+ '116': '11-6',
+ '767484100': '76.748.410-0',
+ '78.412.790-7': '78.412.790-7',
+ '8.334.6043': '8.334.604-3',
+ '76793310-K': '76.793.310-K',
+ }
+ invalid = {
+ '11.111.111-0': error_invalid,
+ '111': error_invalid,
+ }
+ self.assertFieldOutput(CLRutField, valid, invalid)
+
+ # deal with special "Strict Mode".
+ invalid = {
+ '11-6': error_format,
+ '767484100': error_format,
+ '8.334.6043': error_format,
+ '76793310-K': error_format,
+ '11.111.111-0': error_invalid
+ }
+ self.assertFieldOutput(CLRutField,
+ {}, invalid, field_kwargs={"strict": True}
+ )
diff --git a/parts/django/tests/regressiontests/forms/localflavor/cz.py b/parts/django/tests/regressiontests/forms/localflavor/cz.py
new file mode 100644
index 0000000..4cff172
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/cz.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+# Tests for the contrib/localflavor/ CZ Form Fields
+
+tests = r"""
+# CZPostalCodeField #########################################################
+
+>>> from django.contrib.localflavor.cz.forms import CZPostalCodeField
+>>> f = CZPostalCodeField()
+>>> f.clean('84545x')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.']
+>>> f.clean('91909')
+u'91909'
+>>> f.clean('917 01')
+u'91701'
+>>> f.clean('12345')
+u'12345'
+>>> f.clean('123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.']
+>>> f.clean('1234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.']
+>>> f.clean('123 4')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.']
+
+# CZRegionSelect ############################################################
+
+>>> from django.contrib.localflavor.cz.forms import CZRegionSelect
+>>> w = CZRegionSelect()
+>>> w.render('regions', 'TT')
+u'<select name="regions">\n<option value="PR">Prague</option>\n<option value="CE">Central Bohemian Region</option>\n<option value="SO">South Bohemian Region</option>\n<option value="PI">Pilsen Region</option>\n<option value="CA">Carlsbad Region</option>\n<option value="US">Usti Region</option>\n<option value="LB">Liberec Region</option>\n<option value="HK">Hradec Region</option>\n<option value="PA">Pardubice Region</option>\n<option value="VY">Vysocina Region</option>\n<option value="SM">South Moravian Region</option>\n<option value="OL">Olomouc Region</option>\n<option value="ZL">Zlin Region</option>\n<option value="MS">Moravian-Silesian Region</option>\n</select>'
+
+# CZBirthNumberField ########################################################
+
+>>> from django.contrib.localflavor.cz.forms import CZBirthNumberField
+>>> f = CZBirthNumberField()
+>>> f.clean('880523/1237')
+u'880523/1237'
+>>> f.clean('8805231237')
+u'8805231237'
+>>> f.clean('880523/000')
+u'880523/000'
+>>> f.clean('880523000')
+u'880523000'
+>>> f.clean('882101/0011')
+u'882101/0011'
+>>> f.clean('880523/1237', 'm')
+u'880523/1237'
+>>> f.clean('885523/1231', 'f')
+u'885523/1231'
+>>> f.clean('123456/12')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.']
+>>> f.clean('123456/12345')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.']
+>>> f.clean('12345612')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.']
+>>> f.clean('12345612345')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.']
+>>> f.clean('881523/0000', 'm')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+>>> f.clean('885223/0000', 'm')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+>>> f.clean('881223/0000', 'f')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+>>> f.clean('886523/0000', 'f')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+>>> f.clean('880523/1239')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+>>> f.clean('8805231239')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+>>> f.clean('990101/0011')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid birth number.']
+
+# CZICNumberField ########################################################
+
+>>> from django.contrib.localflavor.cz.forms import CZICNumberField
+>>> f = CZICNumberField()
+>>> f.clean('12345679')
+u'12345679'
+>>> f.clean('12345601')
+u'12345601'
+>>> f.clean('12345661')
+u'12345661'
+>>> f.clean('12345610')
+u'12345610'
+>>> f.clean('1234567')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IC number.']
+>>> f.clean('12345660')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IC number.']
+>>> f.clean('12345600')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IC number.']
+"""
diff --git a/parts/django/tests/regressiontests/forms/localflavor/de.py b/parts/django/tests/regressiontests/forms/localflavor/de.py
new file mode 100644
index 0000000..7c68bcc
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/de.py
@@ -0,0 +1,49 @@
+from django.contrib.localflavor.de.forms import (DEZipCodeField, DEStateSelect,
+ DEIdentityCardNumberField)
+
+from utils import LocalFlavorTestCase
+
+
+class DELocalFlavorTests(LocalFlavorTestCase):
+ def test_DEStateSelect(self):
+ f = DEStateSelect()
+ out = u'''<select name="states">
+<option value="BW">Baden-Wuerttemberg</option>
+<option value="BY">Bavaria</option>
+<option value="BE">Berlin</option>
+<option value="BB">Brandenburg</option>
+<option value="HB">Bremen</option>
+<option value="HH">Hamburg</option>
+<option value="HE">Hessen</option>
+<option value="MV">Mecklenburg-Western Pomerania</option>
+<option value="NI">Lower Saxony</option>
+<option value="NW">North Rhine-Westphalia</option>
+<option value="RP">Rhineland-Palatinate</option>
+<option value="SL">Saarland</option>
+<option value="SN">Saxony</option>
+<option value="ST">Saxony-Anhalt</option>
+<option value="SH">Schleswig-Holstein</option>
+<option value="TH" selected="selected">Thuringia</option>
+</select>'''
+ self.assertEqual(f.render('states', 'TH'), out)
+
+ def test_DEZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXXX.']
+ valid = {
+ '99423': '99423',
+ }
+ invalid = {
+ ' 99423': error_format,
+ }
+ self.assertFieldOutput(DEZipCodeField, valid, invalid)
+
+ def test_DEIdentityCardNumberField(self):
+ error_format = [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.']
+ valid = {
+ '7549313035D-6004103-0903042-0': '7549313035D-6004103-0903042-0',
+ '9786324830D 6104243 0910271 2': '9786324830D-6104243-0910271-2',
+ }
+ invalid = {
+ '0434657485D-6407276-0508137-9': error_format,
+ }
+ self.assertFieldOutput(DEIdentityCardNumberField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/es.py b/parts/django/tests/regressiontests/forms/localflavor/es.py
new file mode 100644
index 0000000..b584075
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/es.py
@@ -0,0 +1,172 @@
+from django.contrib.localflavor.es.forms import (ESPostalCodeField, ESPhoneNumberField,
+ ESIdentityCardNumberField, ESCCCField, ESRegionSelect, ESProvinceSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class ESLocalFlavorTests(LocalFlavorTestCase):
+ def test_ESRegionSelect(self):
+ f = ESRegionSelect()
+ out = u'''<select name="regions">
+<option value="AN">Andalusia</option>
+<option value="AR">Aragon</option>
+<option value="O">Principality of Asturias</option>
+<option value="IB">Balearic Islands</option>
+<option value="PV">Basque Country</option>
+<option value="CN">Canary Islands</option>
+<option value="S">Cantabria</option>
+<option value="CM">Castile-La Mancha</option>
+<option value="CL">Castile and Leon</option>
+<option value="CT" selected="selected">Catalonia</option>
+<option value="EX">Extremadura</option>
+<option value="GA">Galicia</option>
+<option value="LO">La Rioja</option>
+<option value="M">Madrid</option>
+<option value="MU">Region of Murcia</option>
+<option value="NA">Foral Community of Navarre</option>
+<option value="VC">Valencian Community</option>
+</select>'''
+ self.assertEqual(f.render('regions', 'CT'), out)
+
+ def test_ESProvinceSelect(self):
+ f = ESProvinceSelect()
+ out = u'''<select name="provinces">
+<option value="01">Arava</option>
+<option value="02">Albacete</option>
+<option value="03">Alacant</option>
+<option value="04">Almeria</option>
+<option value="05">Avila</option>
+<option value="06">Badajoz</option>
+<option value="07">Illes Balears</option>
+<option value="08" selected="selected">Barcelona</option>
+<option value="09">Burgos</option>
+<option value="10">Caceres</option>
+<option value="11">Cadiz</option>
+<option value="12">Castello</option>
+<option value="13">Ciudad Real</option>
+<option value="14">Cordoba</option>
+<option value="15">A Coruna</option>
+<option value="16">Cuenca</option>
+<option value="17">Girona</option>
+<option value="18">Granada</option>
+<option value="19">Guadalajara</option>
+<option value="20">Guipuzkoa</option>
+<option value="21">Huelva</option>
+<option value="22">Huesca</option>
+<option value="23">Jaen</option>
+<option value="24">Leon</option>
+<option value="25">Lleida</option>
+<option value="26">La Rioja</option>
+<option value="27">Lugo</option>
+<option value="28">Madrid</option>
+<option value="29">Malaga</option>
+<option value="30">Murcia</option>
+<option value="31">Navarre</option>
+<option value="32">Ourense</option>
+<option value="33">Asturias</option>
+<option value="34">Palencia</option>
+<option value="35">Las Palmas</option>
+<option value="36">Pontevedra</option>
+<option value="37">Salamanca</option>
+<option value="38">Santa Cruz de Tenerife</option>
+<option value="39">Cantabria</option>
+<option value="40">Segovia</option>
+<option value="41">Seville</option>
+<option value="42">Soria</option>
+<option value="43">Tarragona</option>
+<option value="44">Teruel</option>
+<option value="45">Toledo</option>
+<option value="46">Valencia</option>
+<option value="47">Valladolid</option>
+<option value="48">Bizkaia</option>
+<option value="49">Zamora</option>
+<option value="50">Zaragoza</option>
+<option value="51">Ceuta</option>
+<option value="52">Melilla</option>
+</select>'''
+ self.assertEqual(f.render('provinces', '08'), out)
+
+ def test_ESPostalCodeField(self):
+ error_invalid = [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
+ valid = {
+ '08028': '08028',
+ '28080': '28080',
+ }
+ invalid = {
+ '53001': error_invalid,
+ '0801': error_invalid,
+ '080001': error_invalid,
+ '00999': error_invalid,
+ '08 01': error_invalid,
+ '08A01': error_invalid,
+ }
+ self.assertFieldOutput(ESPostalCodeField, valid, invalid)
+
+ def test_ESPhoneNumberField(self):
+ error_invalid = [u'Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX.']
+ valid = {
+ '650010101': '650010101',
+ '931234567': '931234567',
+ '800123123': '800123123',
+ }
+ invalid = {
+ '555555555': error_invalid,
+ '789789789': error_invalid,
+ '99123123': error_invalid,
+ '9999123123': error_invalid,
+ }
+ self.assertFieldOutput(ESPhoneNumberField, valid, invalid)
+
+ def test_ESIdentityCardNumberField(self):
+ error_invalid = [u'Please enter a valid NIF, NIE, or CIF.']
+ error_checksum_nif = [u'Invalid checksum for NIF.']
+ error_checksum_nie = [u'Invalid checksum for NIE.']
+ error_checksum_cif = [u'Invalid checksum for CIF.']
+ valid = {
+ '78699688J': '78699688J',
+ '78699688-J': '78699688J',
+ '78699688 J': '78699688J',
+ '78699688 j': '78699688J',
+ 'X0901797J': 'X0901797J',
+ 'X-6124387-Q': 'X6124387Q',
+ 'X 0012953 G': 'X0012953G',
+ 'x-3287690-r': 'X3287690R',
+ 't-03287690r': 'T03287690R',
+ 'P2907500I': 'P2907500I',
+ 'B38790911': 'B38790911',
+ 'B31234560': 'B31234560',
+ 'B-3879091A': 'B3879091A',
+ 'B 38790911': 'B38790911',
+ 'P-3900800-H': 'P3900800H',
+ 'P 39008008': 'P39008008',
+ 'C-28795565': 'C28795565',
+ 'C 2879556E': 'C2879556E',
+ }
+ invalid = {
+ '78699688T': error_checksum_nif,
+ 'X-03287690': error_invalid,
+ 'X-03287690-T': error_checksum_nie,
+ 'B 38790917': error_checksum_cif,
+ 'C28795567': error_checksum_cif,
+ 'I38790911': error_invalid,
+ '78699688-2': error_invalid,
+ }
+ self.assertFieldOutput(ESIdentityCardNumberField, valid, invalid)
+
+ def test_ESCCCField(self):
+ error_invalid = [u'Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX.']
+ error_checksum = [u'Invalid checksum for bank account number.']
+ valid = {
+ '20770338793100254321': '20770338793100254321',
+ '2077 0338 79 3100254321': '2077 0338 79 3100254321',
+ '2077-0338-79-3100254321': '2077-0338-79-3100254321',
+ }
+ invalid = {
+ '2077.0338.79.3100254321': error_invalid,
+ '2077-0338-78-3100254321': error_checksum,
+ '2077-0338-89-3100254321': error_checksum,
+ '2077-03-3879-3100254321': error_invalid,
+ }
+ self.assertFieldOutput(ESCCCField, valid, invalid)
+
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/fi.py b/parts/django/tests/regressiontests/forms/localflavor/fi.py
new file mode 100644
index 0000000..161eb17
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/fi.py
@@ -0,0 +1,382 @@
+from django.contrib.localflavor.fi.forms import (FIZipCodeField,
+ FISocialSecurityNumber, FIMunicipalitySelect)
+
+from utils import LocalFlavorTestCase
+
+
+class FILocalFlavorTests(LocalFlavorTestCase):
+ def test_FIMunicipalitySelect(self):
+ f = FIMunicipalitySelect()
+ out = u'''<select name="municipalities">
+<option value="akaa">Akaa</option>
+<option value="alajarvi">Alaj\xe4rvi</option>
+<option value="alavieska">Alavieska</option>
+<option value="alavus">Alavus</option>
+<option value="artjarvi">Artj\xe4rvi</option>
+<option value="asikkala">Asikkala</option>
+<option value="askola">Askola</option>
+<option value="aura">Aura</option>
+<option value="brando">Br\xe4nd\xf6</option>
+<option value="eckero">Ecker\xf6</option>
+<option value="enonkoski">Enonkoski</option>
+<option value="enontekio">Enonteki\xf6</option>
+<option value="espoo">Espoo</option>
+<option value="eura">Eura</option>
+<option value="eurajoki">Eurajoki</option>
+<option value="evijarvi">Evij\xe4rvi</option>
+<option value="finstrom">Finstr\xf6m</option>
+<option value="forssa">Forssa</option>
+<option value="foglo">F\xf6gl\xf6</option>
+<option value="geta">Geta</option>
+<option value="haapajarvi">Haapaj\xe4rvi</option>
+<option value="haapavesi">Haapavesi</option>
+<option value="hailuoto">Hailuoto</option>
+<option value="halsua">Halsua</option>
+<option value="hamina">Hamina</option>
+<option value="hammarland">Hammarland</option>
+<option value="hankasalmi">Hankasalmi</option>
+<option value="hanko">Hanko</option>
+<option value="harjavalta">Harjavalta</option>
+<option value="hartola">Hartola</option>
+<option value="hattula">Hattula</option>
+<option value="haukipudas">Haukipudas</option>
+<option value="hausjarvi">Hausj\xe4rvi</option>
+<option value="heinola">Heinola</option>
+<option value="heinavesi">Hein\xe4vesi</option>
+<option value="helsinki">Helsinki</option>
+<option value="hirvensalmi">Hirvensalmi</option>
+<option value="hollola">Hollola</option>
+<option value="honkajoki">Honkajoki</option>
+<option value="huittinen">Huittinen</option>
+<option value="humppila">Humppila</option>
+<option value="hyrynsalmi">Hyrynsalmi</option>
+<option value="hyvinkaa">Hyvink\xe4\xe4</option>
+<option value="hameenkoski">H\xe4meenkoski</option>
+<option value="hameenkyro">H\xe4meenkyr\xf6</option>
+<option value="hameenlinna">H\xe4meenlinna</option>
+<option value="ii">Ii</option>
+<option value="iisalmi">Iisalmi</option>
+<option value="iitti">Iitti</option>
+<option value="ikaalinen">Ikaalinen</option>
+<option value="ilmajoki">Ilmajoki</option>
+<option value="ilomantsi">Ilomantsi</option>
+<option value="imatra">Imatra</option>
+<option value="inari">Inari</option>
+<option value="inkoo">Inkoo</option>
+<option value="isojoki">Isojoki</option>
+<option value="isokyro">Isokyr\xf6</option>
+<option value="jalasjarvi">Jalasj\xe4rvi</option>
+<option value="janakkala">Janakkala</option>
+<option value="joensuu">Joensuu</option>
+<option value="jokioinen">Jokioinen</option>
+<option value="jomala">Jomala</option>
+<option value="joroinen">Joroinen</option>
+<option value="joutsa">Joutsa</option>
+<option value="juankoski">Juankoski</option>
+<option value="juuka">Juuka</option>
+<option value="juupajoki">Juupajoki</option>
+<option value="juva">Juva</option>
+<option value="jyvaskyla">Jyv\xe4skyl\xe4</option>
+<option value="jamijarvi">J\xe4mij\xe4rvi</option>
+<option value="jamsa">J\xe4ms\xe4</option>
+<option value="jarvenpaa">J\xe4rvenp\xe4\xe4</option>
+<option value="kaarina">Kaarina</option>
+<option value="kaavi">Kaavi</option>
+<option value="kajaani">Kajaani</option>
+<option value="kalajoki">Kalajoki</option>
+<option value="kangasala">Kangasala</option>
+<option value="kangasniemi">Kangasniemi</option>
+<option value="kankaanpaa">Kankaanp\xe4\xe4</option>
+<option value="kannonkoski">Kannonkoski</option>
+<option value="kannus">Kannus</option>
+<option value="karijoki">Karijoki</option>
+<option value="karjalohja">Karjalohja</option>
+<option value="karkkila">Karkkila</option>
+<option value="karstula">Karstula</option>
+<option value="karttula">Karttula</option>
+<option value="karvia">Karvia</option>
+<option value="kaskinen">Kaskinen</option>
+<option value="kauhajoki">Kauhajoki</option>
+<option value="kauhava">Kauhava</option>
+<option value="kauniainen">Kauniainen</option>
+<option value="kaustinen">Kaustinen</option>
+<option value="keitele">Keitele</option>
+<option value="kemi">Kemi</option>
+<option value="kemijarvi">Kemij\xe4rvi</option>
+<option value="keminmaa">Keminmaa</option>
+<option value="kemionsaari">Kemi\xf6nsaari</option>
+<option value="kempele">Kempele</option>
+<option value="kerava">Kerava</option>
+<option value="kerimaki">Kerim\xe4ki</option>
+<option value="kesalahti">Kes\xe4lahti</option>
+<option value="keuruu">Keuruu</option>
+<option value="kihnio">Kihni\xf6</option>
+<option value="kiikoinen">Kiikoinen</option>
+<option value="kiiminki">Kiiminki</option>
+<option value="kinnula">Kinnula</option>
+<option value="kirkkonummi">Kirkkonummi</option>
+<option value="kitee">Kitee</option>
+<option value="kittila">Kittil\xe4</option>
+<option value="kiuruvesi">Kiuruvesi</option>
+<option value="kivijarvi">Kivij\xe4rvi</option>
+<option value="kokemaki">Kokem\xe4ki</option>
+<option value="kokkola">Kokkola</option>
+<option value="kolari">Kolari</option>
+<option value="konnevesi">Konnevesi</option>
+<option value="kontiolahti">Kontiolahti</option>
+<option value="korsnas">Korsn\xe4s</option>
+<option value="koskitl">Koski Tl</option>
+<option value="kotka">Kotka</option>
+<option value="kouvola">Kouvola</option>
+<option value="kristiinankaupunki">Kristiinankaupunki</option>
+<option value="kruunupyy">Kruunupyy</option>
+<option value="kuhmalahti">Kuhmalahti</option>
+<option value="kuhmo">Kuhmo</option>
+<option value="kuhmoinen">Kuhmoinen</option>
+<option value="kumlinge">Kumlinge</option>
+<option value="kuopio">Kuopio</option>
+<option value="kuortane">Kuortane</option>
+<option value="kurikka">Kurikka</option>
+<option value="kustavi">Kustavi</option>
+<option value="kuusamo">Kuusamo</option>
+<option value="kylmakoski">Kylm\xe4koski</option>
+<option value="kyyjarvi">Kyyj\xe4rvi</option>
+<option value="karkola">K\xe4rk\xf6l\xe4</option>
+<option value="karsamaki">K\xe4rs\xe4m\xe4ki</option>
+<option value="kokar">K\xf6kar</option>
+<option value="koylio">K\xf6yli\xf6</option>
+<option value="lahti">Lahti</option>
+<option value="laihia">Laihia</option>
+<option value="laitila">Laitila</option>
+<option value="lapinjarvi">Lapinj\xe4rvi</option>
+<option value="lapinlahti">Lapinlahti</option>
+<option value="lappajarvi">Lappaj\xe4rvi</option>
+<option value="lappeenranta">Lappeenranta</option>
+<option value="lapua">Lapua</option>
+<option value="laukaa">Laukaa</option>
+<option value="lavia">Lavia</option>
+<option value="lemi">Lemi</option>
+<option value="lemland">Lemland</option>
+<option value="lempaala">Lemp\xe4\xe4l\xe4</option>
+<option value="leppavirta">Lepp\xe4virta</option>
+<option value="lestijarvi">Lestij\xe4rvi</option>
+<option value="lieksa">Lieksa</option>
+<option value="lieto">Lieto</option>
+<option value="liminka">Liminka</option>
+<option value="liperi">Liperi</option>
+<option value="lohja">Lohja</option>
+<option value="loimaa">Loimaa</option>
+<option value="loppi">Loppi</option>
+<option value="loviisa">Loviisa</option>
+<option value="luhanka">Luhanka</option>
+<option value="lumijoki">Lumijoki</option>
+<option value="lumparland">Lumparland</option>
+<option value="luoto">Luoto</option>
+<option value="luumaki">Luum\xe4ki</option>
+<option value="luvia">Luvia</option>
+<option value="lansi-turunmaa">L\xe4nsi-Turunmaa</option>
+<option value="maalahti">Maalahti</option>
+<option value="maaninka">Maaninka</option>
+<option value="maarianhamina">Maarianhamina</option>
+<option value="marttila">Marttila</option>
+<option value="masku">Masku</option>
+<option value="merijarvi">Merij\xe4rvi</option>
+<option value="merikarvia">Merikarvia</option>
+<option value="miehikkala">Miehikk\xe4l\xe4</option>
+<option value="mikkeli">Mikkeli</option>
+<option value="muhos">Muhos</option>
+<option value="multia">Multia</option>
+<option value="muonio">Muonio</option>
+<option value="mustasaari">Mustasaari</option>
+<option value="muurame">Muurame</option>
+<option value="mynamaki">Myn\xe4m\xe4ki</option>
+<option value="myrskyla">Myrskyl\xe4</option>
+<option value="mantsala">M\xe4nts\xe4l\xe4</option>
+<option value="mantta-vilppula">M\xe4ntt\xe4-Vilppula</option>
+<option value="mantyharju">M\xe4ntyharju</option>
+<option value="naantali">Naantali</option>
+<option value="nakkila">Nakkila</option>
+<option value="nastola">Nastola</option>
+<option value="nilsia">Nilsi\xe4</option>
+<option value="nivala">Nivala</option>
+<option value="nokia">Nokia</option>
+<option value="nousiainen">Nousiainen</option>
+<option value="nummi-pusula">Nummi-Pusula</option>
+<option value="nurmes">Nurmes</option>
+<option value="nurmijarvi">Nurmij\xe4rvi</option>
+<option value="narpio">N\xe4rpi\xf6</option>
+<option value="oravainen">Oravainen</option>
+<option value="orimattila">Orimattila</option>
+<option value="oripaa">Orip\xe4\xe4</option>
+<option value="orivesi">Orivesi</option>
+<option value="oulainen">Oulainen</option>
+<option value="oulu">Oulu</option>
+<option value="oulunsalo">Oulunsalo</option>
+<option value="outokumpu">Outokumpu</option>
+<option value="padasjoki">Padasjoki</option>
+<option value="paimio">Paimio</option>
+<option value="paltamo">Paltamo</option>
+<option value="parikkala">Parikkala</option>
+<option value="parkano">Parkano</option>
+<option value="pedersore">Peders\xf6re</option>
+<option value="pelkosenniemi">Pelkosenniemi</option>
+<option value="pello">Pello</option>
+<option value="perho">Perho</option>
+<option value="pertunmaa">Pertunmaa</option>
+<option value="petajavesi">Pet\xe4j\xe4vesi</option>
+<option value="pieksamaki">Pieks\xe4m\xe4ki</option>
+<option value="pielavesi">Pielavesi</option>
+<option value="pietarsaari">Pietarsaari</option>
+<option value="pihtipudas">Pihtipudas</option>
+<option value="pirkkala">Pirkkala</option>
+<option value="polvijarvi">Polvij\xe4rvi</option>
+<option value="pomarkku">Pomarkku</option>
+<option value="pori">Pori</option>
+<option value="pornainen">Pornainen</option>
+<option value="porvoo">Porvoo</option>
+<option value="posio">Posio</option>
+<option value="pudasjarvi">Pudasj\xe4rvi</option>
+<option value="pukkila">Pukkila</option>
+<option value="punkaharju">Punkaharju</option>
+<option value="punkalaidun">Punkalaidun</option>
+<option value="puolanka">Puolanka</option>
+<option value="puumala">Puumala</option>
+<option value="pyhtaa">Pyht\xe4\xe4</option>
+<option value="pyhajoki">Pyh\xe4joki</option>
+<option value="pyhajarvi">Pyh\xe4j\xe4rvi</option>
+<option value="pyhanta">Pyh\xe4nt\xe4</option>
+<option value="pyharanta">Pyh\xe4ranta</option>
+<option value="palkane">P\xe4lk\xe4ne</option>
+<option value="poytya">P\xf6yty\xe4</option>
+<option value="raahe">Raahe</option>
+<option value="raasepori">Raasepori</option>
+<option value="raisio">Raisio</option>
+<option value="rantasalmi">Rantasalmi</option>
+<option value="ranua">Ranua</option>
+<option value="rauma">Rauma</option>
+<option value="rautalampi">Rautalampi</option>
+<option value="rautavaara">Rautavaara</option>
+<option value="rautjarvi">Rautj\xe4rvi</option>
+<option value="reisjarvi">Reisj\xe4rvi</option>
+<option value="riihimaki">Riihim\xe4ki</option>
+<option value="ristiina">Ristiina</option>
+<option value="ristijarvi">Ristij\xe4rvi</option>
+<option value="rovaniemi">Rovaniemi</option>
+<option value="ruokolahti">Ruokolahti</option>
+<option value="ruovesi">Ruovesi</option>
+<option value="rusko">Rusko</option>
+<option value="raakkyla">R\xe4\xe4kkyl\xe4</option>
+<option value="saarijarvi">Saarij\xe4rvi</option>
+<option value="salla">Salla</option>
+<option value="salo">Salo</option>
+<option value="saltvik">Saltvik</option>
+<option value="sastamala">Sastamala</option>
+<option value="sauvo">Sauvo</option>
+<option value="savitaipale">Savitaipale</option>
+<option value="savonlinna">Savonlinna</option>
+<option value="savukoski">Savukoski</option>
+<option value="seinajoki">Sein\xe4joki</option>
+<option value="sievi">Sievi</option>
+<option value="siikainen">Siikainen</option>
+<option value="siikajoki">Siikajoki</option>
+<option value="siikalatva">Siikalatva</option>
+<option value="siilinjarvi">Siilinj\xe4rvi</option>
+<option value="simo">Simo</option>
+<option value="sipoo">Sipoo</option>
+<option value="siuntio">Siuntio</option>
+<option value="sodankyla">Sodankyl\xe4</option>
+<option value="soini">Soini</option>
+<option value="somero">Somero</option>
+<option value="sonkajarvi">Sonkaj\xe4rvi</option>
+<option value="sotkamo">Sotkamo</option>
+<option value="sottunga">Sottunga</option>
+<option value="sulkava">Sulkava</option>
+<option value="sund">Sund</option>
+<option value="suomenniemi">Suomenniemi</option>
+<option value="suomussalmi">Suomussalmi</option>
+<option value="suonenjoki">Suonenjoki</option>
+<option value="sysma">Sysm\xe4</option>
+<option value="sakyla">S\xe4kyl\xe4</option>
+<option value="taipalsaari">Taipalsaari</option>
+<option value="taivalkoski">Taivalkoski</option>
+<option value="taivassalo">Taivassalo</option>
+<option value="tammela">Tammela</option>
+<option value="tampere">Tampere</option>
+<option value="tarvasjoki">Tarvasjoki</option>
+<option value="tervo">Tervo</option>
+<option value="tervola">Tervola</option>
+<option value="teuva">Teuva</option>
+<option value="tohmajarvi">Tohmaj\xe4rvi</option>
+<option value="toholampi">Toholampi</option>
+<option value="toivakka">Toivakka</option>
+<option value="tornio">Tornio</option>
+<option value="turku" selected="selected">Turku</option>
+<option value="tuusniemi">Tuusniemi</option>
+<option value="tuusula">Tuusula</option>
+<option value="tyrnava">Tyrn\xe4v\xe4</option>
+<option value="toysa">T\xf6ys\xe4</option>
+<option value="ulvila">Ulvila</option>
+<option value="urjala">Urjala</option>
+<option value="utajarvi">Utaj\xe4rvi</option>
+<option value="utsjoki">Utsjoki</option>
+<option value="uurainen">Uurainen</option>
+<option value="uusikaarlepyy">Uusikaarlepyy</option>
+<option value="uusikaupunki">Uusikaupunki</option>
+<option value="vaala">Vaala</option>
+<option value="vaasa">Vaasa</option>
+<option value="valkeakoski">Valkeakoski</option>
+<option value="valtimo">Valtimo</option>
+<option value="vantaa">Vantaa</option>
+<option value="varkaus">Varkaus</option>
+<option value="varpaisjarvi">Varpaisj\xe4rvi</option>
+<option value="vehmaa">Vehmaa</option>
+<option value="vesanto">Vesanto</option>
+<option value="vesilahti">Vesilahti</option>
+<option value="veteli">Veteli</option>
+<option value="vierema">Vierem\xe4</option>
+<option value="vihanti">Vihanti</option>
+<option value="vihti">Vihti</option>
+<option value="viitasaari">Viitasaari</option>
+<option value="vimpeli">Vimpeli</option>
+<option value="virolahti">Virolahti</option>
+<option value="virrat">Virrat</option>
+<option value="vardo">V\xe5rd\xf6</option>
+<option value="vahakyro">V\xe4h\xe4kyr\xf6</option>
+<option value="voyri-maksamaa">V\xf6yri-Maksamaa</option>
+<option value="yli-ii">Yli-Ii</option>
+<option value="ylitornio">Ylitornio</option>
+<option value="ylivieska">Ylivieska</option>
+<option value="ylojarvi">Yl\xf6j\xe4rvi</option>
+<option value="ypaja">Yp\xe4j\xe4</option>
+<option value="ahtari">\xc4ht\xe4ri</option>
+<option value="aanekoski">\xc4\xe4nekoski</option>
+</select>'''
+ self.assertEquals(f.render('municipalities', 'turku'), out)
+
+ def test_FIZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXXX.']
+ valid = {
+ '20540': '20540',
+ '20101': '20101',
+ }
+ invalid = {
+ '20s40': error_format,
+ '205401': error_format
+ }
+ self.assertFieldOutput(FIZipCodeField, valid, invalid)
+
+ def test_FISocialSecurityNumber(self):
+ error_invalid = [u'Enter a valid Finnish social security number.']
+ valid = {
+ '010101-0101': '010101-0101',
+ '010101+0101': '010101+0101',
+ '010101A0101': '010101A0101',
+ }
+ invalid = {
+ '101010-0102': error_invalid,
+ '10a010-0101': error_invalid,
+ '101010-0\xe401': error_invalid,
+ '101010b0101': error_invalid,
+ }
+ self.assertFieldOutput(FISocialSecurityNumber, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/fr.py b/parts/django/tests/regressiontests/forms/localflavor/fr.py
new file mode 100644
index 0000000..96045c3
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/fr.py
@@ -0,0 +1,145 @@
+from django.contrib.localflavor.fr.forms import (FRZipCodeField,
+ FRPhoneNumberField, FRDepartmentSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class FRLocalFlavorTests(LocalFlavorTestCase):
+ def test_FRZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXXX.']
+ valid = {
+ '75001': '75001',
+ '93200': '93200',
+ }
+ invalid = {
+ '2A200': error_format,
+ '980001': error_format,
+ }
+ self.assertFieldOutput(FRZipCodeField, valid, invalid)
+
+ def test_FRPhoneNumberField(self):
+ error_format = [u'Phone numbers must be in 0X XX XX XX XX format.']
+ valid = {
+ '01 55 44 58 64': '01 55 44 58 64',
+ '0155445864': '01 55 44 58 64',
+ '01 5544 5864': '01 55 44 58 64',
+ '01 55.44.58.64': '01 55 44 58 64',
+ '01.55.44.58.64': '01 55 44 58 64',
+ }
+ invalid = {
+ '01,55,44,58,64': error_format,
+ '555 015 544': error_format,
+ }
+ self.assertFieldOutput(FRPhoneNumberField, valid, invalid)
+
+ def test_FRDepartmentSelect(self):
+ f = FRDepartmentSelect()
+ out = u'''<select name="dep">
+<option value="01">01 - Ain</option>
+<option value="02">02 - Aisne</option>
+<option value="03">03 - Allier</option>
+<option value="04">04 - Alpes-de-Haute-Provence</option>
+<option value="05">05 - Hautes-Alpes</option>
+<option value="06">06 - Alpes-Maritimes</option>
+<option value="07">07 - Ardeche</option>
+<option value="08">08 - Ardennes</option>
+<option value="09">09 - Ariege</option>
+<option value="10">10 - Aube</option>
+<option value="11">11 - Aude</option>
+<option value="12">12 - Aveyron</option>
+<option value="13">13 - Bouches-du-Rhone</option>
+<option value="14">14 - Calvados</option>
+<option value="15">15 - Cantal</option>
+<option value="16">16 - Charente</option>
+<option value="17">17 - Charente-Maritime</option>
+<option value="18">18 - Cher</option>
+<option value="19">19 - Correze</option>
+<option value="21">21 - Cote-d&#39;Or</option>
+<option value="22">22 - Cotes-d&#39;Armor</option>
+<option value="23">23 - Creuse</option>
+<option value="24">24 - Dordogne</option>
+<option value="25">25 - Doubs</option>
+<option value="26">26 - Drome</option>
+<option value="27">27 - Eure</option>
+<option value="28">28 - Eure-et-Loire</option>
+<option value="29">29 - Finistere</option>
+<option value="2A">2A - Corse-du-Sud</option>
+<option value="2B">2B - Haute-Corse</option>
+<option value="30">30 - Gard</option>
+<option value="31">31 - Haute-Garonne</option>
+<option value="32">32 - Gers</option>
+<option value="33">33 - Gironde</option>
+<option value="34">34 - Herault</option>
+<option value="35">35 - Ille-et-Vilaine</option>
+<option value="36">36 - Indre</option>
+<option value="37">37 - Indre-et-Loire</option>
+<option value="38">38 - Isere</option>
+<option value="39">39 - Jura</option>
+<option value="40">40 - Landes</option>
+<option value="41">41 - Loir-et-Cher</option>
+<option value="42">42 - Loire</option>
+<option value="43">43 - Haute-Loire</option>
+<option value="44">44 - Loire-Atlantique</option>
+<option value="45">45 - Loiret</option>
+<option value="46">46 - Lot</option>
+<option value="47">47 - Lot-et-Garonne</option>
+<option value="48">48 - Lozere</option>
+<option value="49">49 - Maine-et-Loire</option>
+<option value="50">50 - Manche</option>
+<option value="51">51 - Marne</option>
+<option value="52">52 - Haute-Marne</option>
+<option value="53">53 - Mayenne</option>
+<option value="54">54 - Meurthe-et-Moselle</option>
+<option value="55">55 - Meuse</option>
+<option value="56">56 - Morbihan</option>
+<option value="57">57 - Moselle</option>
+<option value="58">58 - Nievre</option>
+<option value="59">59 - Nord</option>
+<option value="60">60 - Oise</option>
+<option value="61">61 - Orne</option>
+<option value="62">62 - Pas-de-Calais</option>
+<option value="63">63 - Puy-de-Dome</option>
+<option value="64">64 - Pyrenees-Atlantiques</option>
+<option value="65">65 - Hautes-Pyrenees</option>
+<option value="66">66 - Pyrenees-Orientales</option>
+<option value="67">67 - Bas-Rhin</option>
+<option value="68">68 - Haut-Rhin</option>
+<option value="69">69 - Rhone</option>
+<option value="70">70 - Haute-Saone</option>
+<option value="71">71 - Saone-et-Loire</option>
+<option value="72">72 - Sarthe</option>
+<option value="73">73 - Savoie</option>
+<option value="74">74 - Haute-Savoie</option>
+<option value="75">75 - Paris</option>
+<option value="76">76 - Seine-Maritime</option>
+<option value="77">77 - Seine-et-Marne</option>
+<option value="78">78 - Yvelines</option>
+<option value="79">79 - Deux-Sevres</option>
+<option value="80">80 - Somme</option>
+<option value="81">81 - Tarn</option>
+<option value="82">82 - Tarn-et-Garonne</option>
+<option value="83">83 - Var</option>
+<option value="84">84 - Vaucluse</option>
+<option value="85">85 - Vendee</option>
+<option value="86">86 - Vienne</option>
+<option value="87">87 - Haute-Vienne</option>
+<option value="88">88 - Vosges</option>
+<option value="89">89 - Yonne</option>
+<option value="90">90 - Territoire de Belfort</option>
+<option value="91">91 - Essonne</option>
+<option value="92">92 - Hauts-de-Seine</option>
+<option value="93">93 - Seine-Saint-Denis</option>
+<option value="94">94 - Val-de-Marne</option>
+<option value="95">95 - Val-d&#39;Oise</option>
+<option value="971">971 - Guadeloupe</option>
+<option value="972">972 - Martinique</option>
+<option value="973">973 - Guyane</option>
+<option value="974">974 - La Reunion</option>
+<option value="975">975 - Saint-Pierre-et-Miquelon</option>
+<option value="976">976 - Mayotte</option>
+<option value="984">984 - Terres Australes et Antarctiques</option>
+<option value="986">986 - Wallis et Futuna</option>
+<option value="987">987 - Polynesie Francaise</option>
+<option value="988">988 - Nouvelle-Caledonie</option>
+</select>'''
+ self.assertEqual(f.render('dep', 'Paris'), out)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/generic.py b/parts/django/tests/regressiontests/forms/localflavor/generic.py
new file mode 100644
index 0000000..f47fc91
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/generic.py
@@ -0,0 +1,88 @@
+import datetime
+
+from django.contrib.localflavor.generic.forms import DateField, DateTimeField
+
+from utils import LocalFlavorTestCase
+
+
+class GenericLocalFlavorTests(LocalFlavorTestCase):
+ def test_GenericDateField(self):
+ error_invalid = [u'Enter a valid date.']
+ valid = {
+ datetime.date(2006, 10, 25): datetime.date(2006, 10, 25),
+ datetime.datetime(2006, 10, 25, 14, 30): datetime.date(2006, 10, 25),
+ datetime.datetime(2006, 10, 25, 14, 30, 59): datetime.date(2006, 10, 25),
+ datetime.datetime(2006, 10, 25, 14, 30, 59, 200): datetime.date(2006, 10, 25),
+ '2006-10-25': datetime.date(2006, 10, 25),
+ '25/10/2006': datetime.date(2006, 10, 25),
+ '25/10/06': datetime.date(2006, 10, 25),
+ 'Oct 25 2006': datetime.date(2006, 10, 25),
+ 'October 25 2006': datetime.date(2006, 10, 25),
+ 'October 25, 2006': datetime.date(2006, 10, 25),
+ '25 October 2006': datetime.date(2006, 10, 25),
+ '25 October, 2006': datetime.date(2006, 10, 25),
+ }
+ invalid = {
+ '2006-4-31': error_invalid,
+ '200a-10-25': error_invalid,
+ '10/25/06': error_invalid,
+ }
+ self.assertFieldOutput(DateField, valid, invalid, empty_value=None)
+
+ # DateField with optional input_formats parameter
+ valid = {
+ datetime.date(2006, 10, 25): datetime.date(2006, 10, 25),
+ datetime.datetime(2006, 10, 25, 14, 30): datetime.date(2006, 10, 25),
+ '2006 10 25': datetime.date(2006, 10, 25),
+ }
+ invalid = {
+ '2006-10-25': error_invalid,
+ '25/10/2006': error_invalid,
+ '25/10/06': error_invalid,
+ }
+ kwargs = {'input_formats':['%Y %m %d'],}
+ self.assertFieldOutput(DateField,
+ valid, invalid, field_kwargs=kwargs, empty_value=None
+ )
+
+ def test_GenericDateTimeField(self):
+ error_invalid = [u'Enter a valid date/time.']
+ valid = {
+ datetime.date(2006, 10, 25): datetime.datetime(2006, 10, 25, 0, 0),
+ datetime.datetime(2006, 10, 25, 14, 30): datetime.datetime(2006, 10, 25, 14, 30),
+ datetime.datetime(2006, 10, 25, 14, 30, 59): datetime.datetime(2006, 10, 25, 14, 30, 59),
+ datetime.datetime(2006, 10, 25, 14, 30, 59, 200): datetime.datetime(2006, 10, 25, 14, 30, 59, 200),
+ '2006-10-25 14:30:45': datetime.datetime(2006, 10, 25, 14, 30, 45),
+ '2006-10-25 14:30:00': datetime.datetime(2006, 10, 25, 14, 30),
+ '2006-10-25 14:30': datetime.datetime(2006, 10, 25, 14, 30),
+ '2006-10-25': datetime.datetime(2006, 10, 25, 0, 0),
+ '25/10/2006 14:30:45': datetime.datetime(2006, 10, 25, 14, 30, 45),
+ '25/10/2006 14:30:00': datetime.datetime(2006, 10, 25, 14, 30),
+ '25/10/2006 14:30': datetime.datetime(2006, 10, 25, 14, 30),
+ '25/10/2006': datetime.datetime(2006, 10, 25, 0, 0),
+ '25/10/06 14:30:45': datetime.datetime(2006, 10, 25, 14, 30, 45),
+ '25/10/06 14:30:00': datetime.datetime(2006, 10, 25, 14, 30),
+ '25/10/06 14:30': datetime.datetime(2006, 10, 25, 14, 30),
+ '25/10/06': datetime.datetime(2006, 10, 25, 0, 0),
+ }
+ invalid = {
+ 'hello': error_invalid,
+ '2006-10-25 4:30 p.m.': error_invalid,
+ }
+ self.assertFieldOutput(DateTimeField, valid, invalid, empty_value=None)
+
+ # DateTimeField with optional input_formats paramter
+ valid = {
+ datetime.date(2006, 10, 25): datetime.datetime(2006, 10, 25, 0, 0),
+ datetime.datetime(2006, 10, 25, 14, 30): datetime.datetime(2006, 10, 25, 14, 30),
+ datetime.datetime(2006, 10, 25, 14, 30, 59): datetime.datetime(2006, 10, 25, 14, 30, 59),
+ datetime.datetime(2006, 10, 25, 14, 30, 59, 200): datetime.datetime(2006, 10, 25, 14, 30, 59, 200),
+ '2006 10 25 2:30 PM': datetime.datetime(2006, 10, 25, 14, 30),
+ }
+ invalid = {
+ '2006-10-25 14:30:45': error_invalid,
+ }
+ kwargs = {'input_formats':['%Y %m %d %I:%M %p'],}
+ self.assertFieldOutput(DateTimeField,
+ valid, invalid, field_kwargs=kwargs, empty_value=None
+ )
diff --git a/parts/django/tests/regressiontests/forms/localflavor/id.py b/parts/django/tests/regressiontests/forms/localflavor/id.py
new file mode 100644
index 0000000..cb346ef
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/id.py
@@ -0,0 +1,181 @@
+from django.contrib.localflavor.id.forms import (IDPhoneNumberField,
+ IDPostCodeField, IDNationalIdentityNumberField, IDLicensePlateField,
+ IDProvinceSelect, IDLicensePlatePrefixSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class IDLocalFlavorTests(LocalFlavorTestCase):
+ def test_IDProvinceSelect(self):
+ f = IDProvinceSelect()
+ out = u'''<select name="provinces">
+<option value="BLI">Bali</option>
+<option value="BTN">Banten</option>
+<option value="BKL">Bengkulu</option>
+<option value="DIY">Yogyakarta</option>
+<option value="JKT">Jakarta</option>
+<option value="GOR">Gorontalo</option>
+<option value="JMB">Jambi</option>
+<option value="JBR">Jawa Barat</option>
+<option value="JTG">Jawa Tengah</option>
+<option value="JTM">Jawa Timur</option>
+<option value="KBR">Kalimantan Barat</option>
+<option value="KSL">Kalimantan Selatan</option>
+<option value="KTG">Kalimantan Tengah</option>
+<option value="KTM">Kalimantan Timur</option>
+<option value="BBL">Kepulauan Bangka-Belitung</option>
+<option value="KRI">Kepulauan Riau</option>
+<option value="LPG" selected="selected">Lampung</option>
+<option value="MLK">Maluku</option>
+<option value="MUT">Maluku Utara</option>
+<option value="NAD">Nanggroe Aceh Darussalam</option>
+<option value="NTB">Nusa Tenggara Barat</option>
+<option value="NTT">Nusa Tenggara Timur</option>
+<option value="PPA">Papua</option>
+<option value="PPB">Papua Barat</option>
+<option value="RIU">Riau</option>
+<option value="SLB">Sulawesi Barat</option>
+<option value="SLS">Sulawesi Selatan</option>
+<option value="SLT">Sulawesi Tengah</option>
+<option value="SLR">Sulawesi Tenggara</option>
+<option value="SLU">Sulawesi Utara</option>
+<option value="SMB">Sumatera Barat</option>
+<option value="SMS">Sumatera Selatan</option>
+<option value="SMU">Sumatera Utara</option>
+</select>'''
+ self.assertEqual(f.render('provinces', 'LPG'), out)
+
+ def test_IDLicensePlatePrefixSelect(self):
+ f = IDLicensePlatePrefixSelect()
+ out = u'''<select name="codes">
+<option value="A">Banten</option>
+<option value="AA">Magelang</option>
+<option value="AB">Yogyakarta</option>
+<option value="AD">Surakarta - Solo</option>
+<option value="AE">Madiun</option>
+<option value="AG">Kediri</option>
+<option value="B">Jakarta</option>
+<option value="BA">Sumatera Barat</option>
+<option value="BB">Tapanuli</option>
+<option value="BD">Bengkulu</option>
+<option value="BE" selected="selected">Lampung</option>
+<option value="BG">Sumatera Selatan</option>
+<option value="BH">Jambi</option>
+<option value="BK">Sumatera Utara</option>
+<option value="BL">Nanggroe Aceh Darussalam</option>
+<option value="BM">Riau</option>
+<option value="BN">Kepulauan Bangka Belitung</option>
+<option value="BP">Kepulauan Riau</option>
+<option value="CC">Corps Consulate</option>
+<option value="CD">Corps Diplomatic</option>
+<option value="D">Bandung</option>
+<option value="DA">Kalimantan Selatan</option>
+<option value="DB">Sulawesi Utara Daratan</option>
+<option value="DC">Sulawesi Barat</option>
+<option value="DD">Sulawesi Selatan</option>
+<option value="DE">Maluku</option>
+<option value="DG">Maluku Utara</option>
+<option value="DH">NTT - Timor</option>
+<option value="DK">Bali</option>
+<option value="DL">Sulawesi Utara Kepulauan</option>
+<option value="DM">Gorontalo</option>
+<option value="DN">Sulawesi Tengah</option>
+<option value="DR">NTB - Lombok</option>
+<option value="DS">Papua dan Papua Barat</option>
+<option value="DT">Sulawesi Tenggara</option>
+<option value="E">Cirebon</option>
+<option value="EA">NTB - Sumbawa</option>
+<option value="EB">NTT - Flores</option>
+<option value="ED">NTT - Sumba</option>
+<option value="F">Bogor</option>
+<option value="G">Pekalongan</option>
+<option value="H">Semarang</option>
+<option value="K">Pati</option>
+<option value="KB">Kalimantan Barat</option>
+<option value="KH">Kalimantan Tengah</option>
+<option value="KT">Kalimantan Timur</option>
+<option value="L">Surabaya</option>
+<option value="M">Madura</option>
+<option value="N">Malang</option>
+<option value="P">Jember</option>
+<option value="R">Banyumas</option>
+<option value="RI">Federal Government</option>
+<option value="S">Bojonegoro</option>
+<option value="T">Purwakarta</option>
+<option value="W">Sidoarjo</option>
+<option value="Z">Garut</option>
+</select>'''
+ self.assertEqual(f.render('codes', 'BE'), out)
+
+ def test_IDPhoneNumberField(self):
+ error_invalid = [u'Enter a valid phone number']
+ valid = {
+ '0812-3456789': u'0812-3456789',
+ '081234567890': u'081234567890',
+ '021 345 6789': u'021 345 6789',
+ '0213456789': u'0213456789',
+ '+62-21-3456789': u'+62-21-3456789',
+ '(021) 345 6789': u'(021) 345 6789',
+ }
+ invalid = {
+ '0123456789': error_invalid,
+ '+62-021-3456789': error_invalid,
+ '+62-021-3456789': error_invalid,
+ '+62-0812-3456789': error_invalid,
+ '0812345678901': error_invalid,
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(IDPhoneNumberField, valid, invalid)
+
+ def test_IDPostCodeField(self):
+ error_invalid = [u'Enter a valid post code']
+ valid = {
+ '12340': u'12340',
+ '25412': u'25412',
+ ' 12340 ': u'12340',
+ }
+ invalid = {
+ '12 3 4 0': error_invalid,
+ '12345': error_invalid,
+ '10100': error_invalid,
+ '123456': error_invalid,
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(IDPostCodeField, valid, invalid)
+
+ def test_IDNationalIdentityNumberField(self):
+ error_invalid = [u'Enter a valid NIK/KTP number']
+ valid = {
+ ' 12.3456.010178 3456 ': u'12.3456.010178.3456',
+ '1234560101783456': u'12.3456.010178.3456',
+ '12.3456.010101.3456': u'12.3456.010101.3456',
+ }
+ invalid = {
+ '12.3456.310278.3456': error_invalid,
+ '00.0000.010101.0000': error_invalid,
+ '1234567890123456': error_invalid,
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(IDNationalIdentityNumberField, valid, invalid)
+
+ def test_IDLicensePlateField(self):
+ error_invalid = [u'Enter a valid vehicle license plate number']
+ valid = {
+ ' b 1234 ab ': u'B 1234 AB',
+ 'B 1234 ABC': u'B 1234 ABC',
+ 'A 12': u'A 12',
+ 'DK 12345 12': u'DK 12345 12',
+ 'RI 10': u'RI 10',
+ 'CD 12 12': u'CD 12 12',
+ }
+ invalid = {
+ 'CD 10 12': error_invalid,
+ 'CD 1234 12': error_invalid,
+ 'RI 10 AB': error_invalid,
+ 'B 12345 01': error_invalid,
+ 'N 1234 12': error_invalid,
+ 'A 12 XYZ': error_invalid,
+ 'Q 1234 AB': error_invalid,
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(IDLicensePlateField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/ie.py b/parts/django/tests/regressiontests/forms/localflavor/ie.py
new file mode 100644
index 0000000..fab519b
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/ie.py
@@ -0,0 +1,43 @@
+from django.contrib.localflavor.ie.forms import IECountySelect
+
+from utils import LocalFlavorTestCase
+
+
+class IELocalFlavorTests(LocalFlavorTestCase):
+ def test_IECountySelect(self):
+ f = IECountySelect()
+ out = u'''<select name="counties">
+<option value="antrim">Antrim</option>
+<option value="armagh">Armagh</option>
+<option value="carlow">Carlow</option>
+<option value="cavan">Cavan</option>
+<option value="clare">Clare</option>
+<option value="cork">Cork</option>
+<option value="derry">Derry</option>
+<option value="donegal">Donegal</option>
+<option value="down">Down</option>
+<option value="dublin" selected="selected">Dublin</option>
+<option value="fermanagh">Fermanagh</option>
+<option value="galway">Galway</option>
+<option value="kerry">Kerry</option>
+<option value="kildare">Kildare</option>
+<option value="kilkenny">Kilkenny</option>
+<option value="laois">Laois</option>
+<option value="leitrim">Leitrim</option>
+<option value="limerick">Limerick</option>
+<option value="longford">Longford</option>
+<option value="louth">Louth</option>
+<option value="mayo">Mayo</option>
+<option value="meath">Meath</option>
+<option value="monaghan">Monaghan</option>
+<option value="offaly">Offaly</option>
+<option value="roscommon">Roscommon</option>
+<option value="sligo">Sligo</option>
+<option value="tipperary">Tipperary</option>
+<option value="tyrone">Tyrone</option>
+<option value="waterford">Waterford</option>
+<option value="westmeath">Westmeath</option>
+<option value="wexford">Wexford</option>
+<option value="wicklow">Wicklow</option>
+</select>'''
+ self.assertEqual(f.render('counties', 'dublin'), out)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/is_.py b/parts/django/tests/regressiontests/forms/localflavor/is_.py
new file mode 100644
index 0000000..fa6b133
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/is_.py
@@ -0,0 +1,199 @@
+from django.contrib.localflavor.is_.forms import (ISIdNumberField,
+ ISPhoneNumberField, ISPostalCodeSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class ISLocalFlavorTests(LocalFlavorTestCase):
+ def test_ISPostalCodeSelect(self):
+ f = ISPostalCodeSelect()
+ out = u'''<select name="foo">
+<option value="101">101 Reykjav\xedk</option>
+<option value="103">103 Reykjav\xedk</option>
+<option value="104">104 Reykjav\xedk</option>
+<option value="105">105 Reykjav\xedk</option>
+<option value="107">107 Reykjav\xedk</option>
+<option value="108">108 Reykjav\xedk</option>
+<option value="109">109 Reykjav\xedk</option>
+<option value="110">110 Reykjav\xedk</option>
+<option value="111">111 Reykjav\xedk</option>
+<option value="112">112 Reykjav\xedk</option>
+<option value="113">113 Reykjav\xedk</option>
+<option value="116">116 Kjalarnes</option>
+<option value="121">121 Reykjav\xedk</option>
+<option value="123">123 Reykjav\xedk</option>
+<option value="124">124 Reykjav\xedk</option>
+<option value="125">125 Reykjav\xedk</option>
+<option value="127">127 Reykjav\xedk</option>
+<option value="128">128 Reykjav\xedk</option>
+<option value="129">129 Reykjav\xedk</option>
+<option value="130">130 Reykjav\xedk</option>
+<option value="132">132 Reykjav\xedk</option>
+<option value="150">150 Reykjav\xedk</option>
+<option value="155">155 Reykjav\xedk</option>
+<option value="170">170 Seltjarnarnes</option>
+<option value="172">172 Seltjarnarnes</option>
+<option value="190">190 Vogar</option>
+<option value="200">200 K\xf3pavogur</option>
+<option value="201">201 K\xf3pavogur</option>
+<option value="202">202 K\xf3pavogur</option>
+<option value="203">203 K\xf3pavogur</option>
+<option value="210">210 Gar\xf0ab\xe6r</option>
+<option value="212">212 Gar\xf0ab\xe6r</option>
+<option value="220">220 Hafnarfj\xf6r\xf0ur</option>
+<option value="221">221 Hafnarfj\xf6r\xf0ur</option>
+<option value="222">222 Hafnarfj\xf6r\xf0ur</option>
+<option value="225">225 \xc1lftanes</option>
+<option value="230">230 Reykjanesb\xe6r</option>
+<option value="232">232 Reykjanesb\xe6r</option>
+<option value="233">233 Reykjanesb\xe6r</option>
+<option value="235">235 Keflav\xedkurflugv\xf6llur</option>
+<option value="240">240 Grindav\xedk</option>
+<option value="245">245 Sandger\xf0i</option>
+<option value="250">250 Gar\xf0ur</option>
+<option value="260">260 Reykjanesb\xe6r</option>
+<option value="270">270 Mosfellsb\xe6r</option>
+<option value="300">300 Akranes</option>
+<option value="301">301 Akranes</option>
+<option value="302">302 Akranes</option>
+<option value="310">310 Borgarnes</option>
+<option value="311">311 Borgarnes</option>
+<option value="320">320 Reykholt \xed Borgarfir\xf0i</option>
+<option value="340">340 Stykkish\xf3lmur</option>
+<option value="345">345 Flatey \xe1 Brei\xf0afir\xf0i</option>
+<option value="350">350 Grundarfj\xf6r\xf0ur</option>
+<option value="355">355 \xd3lafsv\xedk</option>
+<option value="356">356 Sn\xe6fellsb\xe6r</option>
+<option value="360">360 Hellissandur</option>
+<option value="370">370 B\xfa\xf0ardalur</option>
+<option value="371">371 B\xfa\xf0ardalur</option>
+<option value="380">380 Reykh\xf3lahreppur</option>
+<option value="400">400 \xcdsafj\xf6r\xf0ur</option>
+<option value="401">401 \xcdsafj\xf6r\xf0ur</option>
+<option value="410">410 Hn\xedfsdalur</option>
+<option value="415">415 Bolungarv\xedk</option>
+<option value="420">420 S\xfa\xf0av\xedk</option>
+<option value="425">425 Flateyri</option>
+<option value="430">430 Su\xf0ureyri</option>
+<option value="450">450 Patreksfj\xf6r\xf0ur</option>
+<option value="451">451 Patreksfj\xf6r\xf0ur</option>
+<option value="460">460 T\xe1lknafj\xf6r\xf0ur</option>
+<option value="465">465 B\xedldudalur</option>
+<option value="470">470 \xdeingeyri</option>
+<option value="471">471 \xdeingeyri</option>
+<option value="500">500 Sta\xf0ur</option>
+<option value="510">510 H\xf3lmav\xedk</option>
+<option value="512">512 H\xf3lmav\xedk</option>
+<option value="520">520 Drangsnes</option>
+<option value="522">522 Kj\xf6rvogur</option>
+<option value="523">523 B\xe6r</option>
+<option value="524">524 Nor\xf0urfj\xf6r\xf0ur</option>
+<option value="530">530 Hvammstangi</option>
+<option value="531">531 Hvammstangi</option>
+<option value="540">540 Bl\xf6ndu\xf3s</option>
+<option value="541">541 Bl\xf6ndu\xf3s</option>
+<option value="545">545 Skagastr\xf6nd</option>
+<option value="550">550 Sau\xf0\xe1rkr\xf3kur</option>
+<option value="551">551 Sau\xf0\xe1rkr\xf3kur</option>
+<option value="560">560 Varmahl\xed\xf0</option>
+<option value="565">565 Hofs\xf3s</option>
+<option value="566">566 Hofs\xf3s</option>
+<option value="570">570 Flj\xf3t</option>
+<option value="580">580 Siglufj\xf6r\xf0ur</option>
+<option value="600">600 Akureyri</option>
+<option value="601">601 Akureyri</option>
+<option value="602">602 Akureyri</option>
+<option value="603">603 Akureyri</option>
+<option value="610">610 Greniv\xedk</option>
+<option value="611">611 Gr\xedmsey</option>
+<option value="620">620 Dalv\xedk</option>
+<option value="621">621 Dalv\xedk</option>
+<option value="625">625 \xd3lafsfj\xf6r\xf0ur</option>
+<option value="630">630 Hr\xedsey</option>
+<option value="640">640 H\xfasav\xedk</option>
+<option value="641">641 H\xfasav\xedk</option>
+<option value="645">645 Fossh\xf3ll</option>
+<option value="650">650 Laugar</option>
+<option value="660">660 M\xfdvatn</option>
+<option value="670">670 K\xf3pasker</option>
+<option value="671">671 K\xf3pasker</option>
+<option value="675">675 Raufarh\xf6fn</option>
+<option value="680">680 \xde\xf3rsh\xf6fn</option>
+<option value="681">681 \xde\xf3rsh\xf6fn</option>
+<option value="685">685 Bakkafj\xf6r\xf0ur</option>
+<option value="690">690 Vopnafj\xf6r\xf0ur</option>
+<option value="700">700 Egilssta\xf0ir</option>
+<option value="701">701 Egilssta\xf0ir</option>
+<option value="710">710 Sey\xf0isfj\xf6r\xf0ur</option>
+<option value="715">715 Mj\xf3ifj\xf6r\xf0ur</option>
+<option value="720">720 Borgarfj\xf6r\xf0ur eystri</option>
+<option value="730">730 Rey\xf0arfj\xf6r\xf0ur</option>
+<option value="735">735 Eskifj\xf6r\xf0ur</option>
+<option value="740">740 Neskaupsta\xf0ur</option>
+<option value="750">750 F\xe1skr\xfa\xf0sfj\xf6r\xf0ur</option>
+<option value="755">755 St\xf6\xf0varfj\xf6r\xf0ur</option>
+<option value="760">760 Brei\xf0dalsv\xedk</option>
+<option value="765">765 Dj\xfapivogur</option>
+<option value="780">780 H\xf6fn \xed Hornafir\xf0i</option>
+<option value="781">781 H\xf6fn \xed Hornafir\xf0i</option>
+<option value="785">785 \xd6r\xe6fi</option>
+<option value="800">800 Selfoss</option>
+<option value="801">801 Selfoss</option>
+<option value="802">802 Selfoss</option>
+<option value="810">810 Hverager\xf0i</option>
+<option value="815">815 \xdeorl\xe1ksh\xf6fn</option>
+<option value="820">820 Eyrarbakki</option>
+<option value="825">825 Stokkseyri</option>
+<option value="840">840 Laugarvatn</option>
+<option value="845">845 Fl\xfa\xf0ir</option>
+<option value="850">850 Hella</option>
+<option value="851">851 Hella</option>
+<option value="860">860 Hvolsv\xf6llur</option>
+<option value="861">861 Hvolsv\xf6llur</option>
+<option value="870">870 V\xedk</option>
+<option value="871">871 V\xedk</option>
+<option value="880">880 Kirkjub\xe6jarklaustur</option>
+<option value="900">900 Vestmannaeyjar</option>
+<option value="902">902 Vestmannaeyjar</option>
+</select>'''
+ self.assertEqual(f.render('foo', 'bar'), out)
+
+ def test_ISIdNumberField(self):
+ error_atleast = [u'Ensure this value has at least 10 characters (it has 9).']
+ error_invalid = [u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.']
+ error_atmost = [u'Ensure this value has at most 11 characters (it has 12).']
+ error_notvalid = [u'The Icelandic identification number is not valid.']
+ valid = {
+ '2308803449': '230880-3449',
+ '230880-3449': '230880-3449',
+ '230880 3449': '230880-3449',
+ '2308803440': '230880-3440',
+ }
+ invalid = {
+ '230880343': error_atleast + error_invalid,
+ '230880343234': error_atmost + error_invalid,
+ 'abcdefghijk': error_invalid,
+ '2308803439': error_notvalid,
+
+ }
+ self.assertFieldOutput(ISIdNumberField, valid, invalid)
+
+ def test_ISPhoneNumberField(self):
+ error_invalid = [u'Enter a valid value.']
+ error_atleast = [u'Ensure this value has at least 7 characters (it has 6).']
+ error_atmost = [u'Ensure this value has at most 8 characters (it has 9).']
+ valid = {
+ '1234567': '1234567',
+ '123 4567': '1234567',
+ '123-4567': '1234567',
+ }
+ invalid = {
+ '123-456': error_invalid,
+ '123456': error_atleast + error_invalid,
+ '123456555': error_atmost + error_invalid,
+ 'abcdefg': error_invalid,
+ ' 1234567 ': error_atmost + error_invalid,
+ ' 12367 ': error_invalid
+ }
+ self.assertFieldOutput(ISPhoneNumberField, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/it.py b/parts/django/tests/regressiontests/forms/localflavor/it.py
new file mode 100644
index 0000000..7181e25
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/it.py
@@ -0,0 +1,70 @@
+from django.contrib.localflavor.it.forms import (ITZipCodeField, ITRegionSelect,
+ ITSocialSecurityNumberField, ITVatNumberField)
+
+from utils import LocalFlavorTestCase
+
+
+class ITLocalFlavorTests(LocalFlavorTestCase):
+ def test_ITRegionSelect(self):
+ f = ITRegionSelect()
+ out = u'''<select name="regions">
+<option value="ABR">Abruzzo</option>
+<option value="BAS">Basilicata</option>
+<option value="CAL">Calabria</option>
+<option value="CAM">Campania</option>
+<option value="EMR">Emilia-Romagna</option>
+<option value="FVG">Friuli-Venezia Giulia</option>
+<option value="LAZ">Lazio</option>
+<option value="LIG">Liguria</option>
+<option value="LOM">Lombardia</option>
+<option value="MAR">Marche</option>
+<option value="MOL">Molise</option>
+<option value="PMN" selected="selected">Piemonte</option>
+<option value="PUG">Puglia</option>
+<option value="SAR">Sardegna</option>
+<option value="SIC">Sicilia</option>
+<option value="TOS">Toscana</option>
+<option value="TAA">Trentino-Alto Adige</option>
+<option value="UMB">Umbria</option>
+<option value="VAO">Valle d\u2019Aosta</option>
+<option value="VEN">Veneto</option>
+</select>'''
+ self.assertEqual(f.render('regions', 'PMN'), out)
+
+ def test_ITZipCodeField(self):
+ error_invalid = [u'Enter a valid zip code.']
+ valid = {
+ '00100': '00100',
+ }
+ invalid = {
+ ' 00100': error_invalid,
+ }
+ self.assertFieldOutput(ITZipCodeField, valid, invalid)
+
+ def test_ITSocialSecurityNumberField(self):
+ error_invalid = [u'Enter a valid Social Security number.']
+ valid = {
+ 'LVSGDU99T71H501L': 'LVSGDU99T71H501L',
+ 'LBRRME11A01L736W': 'LBRRME11A01L736W',
+ 'lbrrme11a01l736w': 'LBRRME11A01L736W',
+ 'LBR RME 11A01 L736W': 'LBRRME11A01L736W',
+ }
+ invalid = {
+ 'LBRRME11A01L736A': error_invalid,
+ '%BRRME11A01L736W': error_invalid,
+ }
+ self.assertFieldOutput(ITSocialSecurityNumberField, valid, invalid)
+
+ def test_ITVatNumberField(self):
+ error_invalid = [u'Enter a valid VAT number.']
+ valid = {
+ '07973780013': '07973780013',
+ '7973780013': '07973780013',
+ 7973780013: '07973780013',
+ }
+ invalid = {
+ '07973780014': error_invalid,
+ 'A7973780013': error_invalid,
+ }
+ self.assertFieldOutput(ITVatNumberField, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/jp.py b/parts/django/tests/regressiontests/forms/localflavor/jp.py
new file mode 100644
index 0000000..1f8362a
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/jp.py
@@ -0,0 +1,73 @@
+from django.contrib.localflavor.jp.forms import (JPPostalCodeField,
+ JPPrefectureSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class JPLocalFlavorTests(LocalFlavorTestCase):
+ def test_JPPrefectureSelect(self):
+ f = JPPrefectureSelect()
+ out = u'''<select name="prefecture">
+<option value="hokkaido">Hokkaido</option>
+<option value="aomori">Aomori</option>
+<option value="iwate">Iwate</option>
+<option value="miyagi">Miyagi</option>
+<option value="akita">Akita</option>
+<option value="yamagata">Yamagata</option>
+<option value="fukushima">Fukushima</option>
+<option value="ibaraki">Ibaraki</option>
+<option value="tochigi">Tochigi</option>
+<option value="gunma">Gunma</option>
+<option value="saitama">Saitama</option>
+<option value="chiba">Chiba</option>
+<option value="tokyo">Tokyo</option>
+<option value="kanagawa" selected="selected">Kanagawa</option>
+<option value="yamanashi">Yamanashi</option>
+<option value="nagano">Nagano</option>
+<option value="niigata">Niigata</option>
+<option value="toyama">Toyama</option>
+<option value="ishikawa">Ishikawa</option>
+<option value="fukui">Fukui</option>
+<option value="gifu">Gifu</option>
+<option value="shizuoka">Shizuoka</option>
+<option value="aichi">Aichi</option>
+<option value="mie">Mie</option>
+<option value="shiga">Shiga</option>
+<option value="kyoto">Kyoto</option>
+<option value="osaka">Osaka</option>
+<option value="hyogo">Hyogo</option>
+<option value="nara">Nara</option>
+<option value="wakayama">Wakayama</option>
+<option value="tottori">Tottori</option>
+<option value="shimane">Shimane</option>
+<option value="okayama">Okayama</option>
+<option value="hiroshima">Hiroshima</option>
+<option value="yamaguchi">Yamaguchi</option>
+<option value="tokushima">Tokushima</option>
+<option value="kagawa">Kagawa</option>
+<option value="ehime">Ehime</option>
+<option value="kochi">Kochi</option>
+<option value="fukuoka">Fukuoka</option>
+<option value="saga">Saga</option>
+<option value="nagasaki">Nagasaki</option>
+<option value="kumamoto">Kumamoto</option>
+<option value="oita">Oita</option>
+<option value="miyazaki">Miyazaki</option>
+<option value="kagoshima">Kagoshima</option>
+<option value="okinawa">Okinawa</option>
+</select>'''
+ self.assertEqual(f.render('prefecture', 'kanagawa'), out)
+
+ def test_JPPostalCodeField(self):
+ error_format = [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.']
+ valid = {
+ '251-0032': '2510032',
+ '2510032': '2510032',
+ }
+ invalid = {
+ '2510-032': error_format,
+ '251a0032': error_format,
+ 'a51-0032': error_format,
+ '25100321': error_format,
+ }
+ self.assertFieldOutput(JPPostalCodeField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/kw.py b/parts/django/tests/regressiontests/forms/localflavor/kw.py
new file mode 100644
index 0000000..af998bd
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/kw.py
@@ -0,0 +1,16 @@
+from django.contrib.localflavor.kw.forms import KWCivilIDNumberField
+
+from utils import LocalFlavorTestCase
+
+
+class KWLocalFlavorTests(LocalFlavorTestCase):
+ def test_KWCivilIDNumberField(self):
+ error_invalid = [u'Enter a valid Kuwaiti Civil ID number']
+ valid = {
+ '282040701483': '282040701483',
+ }
+ invalid = {
+ '289332013455': error_invalid,
+ }
+ self.assertFieldOutput(KWCivilIDNumberField, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/nl.py b/parts/django/tests/regressiontests/forms/localflavor/nl.py
new file mode 100644
index 0000000..8ef0ae9
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/nl.py
@@ -0,0 +1,62 @@
+from django.contrib.localflavor.nl.forms import (NLPhoneNumberField,
+ NLZipCodeField, NLSoFiNumberField, NLProvinceSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class NLLocalFlavorTests(LocalFlavorTestCase):
+ def test_NLProvinceSelect(self):
+ f = NLProvinceSelect()
+ out = u'''<select name="provinces">
+<option value="DR">Drenthe</option>
+<option value="FL">Flevoland</option>
+<option value="FR">Friesland</option>
+<option value="GL">Gelderland</option>
+<option value="GR">Groningen</option>
+<option value="LB">Limburg</option>
+<option value="NB">Noord-Brabant</option>
+<option value="NH">Noord-Holland</option>
+<option value="OV" selected="selected">Overijssel</option>
+<option value="UT">Utrecht</option>
+<option value="ZE">Zeeland</option>
+<option value="ZH">Zuid-Holland</option>
+</select>'''
+ self.assertEqual(f.render('provinces', 'OV'), out)
+
+ def test_NLPhoneNumberField(self):
+ error_invalid = [u'Enter a valid phone number']
+ valid = {
+ '012-3456789': '012-3456789',
+ '0123456789': '0123456789',
+ '+31-12-3456789': '+31-12-3456789',
+ '(0123) 456789': '(0123) 456789',
+ }
+ invalid = {
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(NLPhoneNumberField, valid, invalid)
+
+ def test_NLZipCodeField(self):
+ error_invalid = [u'Enter a valid postal code']
+ valid = {
+ '1234ab': '1234 AB',
+ '1234 ab': '1234 AB',
+ '1234 AB': '1234 AB',
+ }
+ invalid = {
+ '0123AB': error_invalid,
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(NLZipCodeField, valid, invalid)
+
+ def test_NLSoFiNumberField(self):
+ error_invalid = [u'Enter a valid SoFi number']
+ valid = {
+ '123456782': '123456782',
+ }
+ invalid = {
+ '000000000': error_invalid,
+ '123456789': error_invalid,
+ 'foo': error_invalid,
+ }
+ self.assertFieldOutput(NLSoFiNumberField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/pl.py b/parts/django/tests/regressiontests/forms/localflavor/pl.py
new file mode 100644
index 0000000..51721f8
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/pl.py
@@ -0,0 +1,462 @@
+from django.contrib.localflavor.pl.forms import (PLProvinceSelect,
+ PLCountySelect, PLPostalCodeField, PLNIPField, PLPESELField, PLREGONField)
+
+from utils import LocalFlavorTestCase
+
+
+class PLLocalFlavorTests(LocalFlavorTestCase):
+ def test_PLProvinceSelect(self):
+ f = PLProvinceSelect()
+ out = u'''<select name="voivodeships">
+<option value="lower_silesia">Lower Silesia</option>
+<option value="kuyavia-pomerania">Kuyavia-Pomerania</option>
+<option value="lublin">Lublin</option>
+<option value="lubusz">Lubusz</option>
+<option value="lodz">Lodz</option>
+<option value="lesser_poland">Lesser Poland</option>
+<option value="masovia">Masovia</option>
+<option value="opole">Opole</option>
+<option value="subcarpatia">Subcarpatia</option>
+<option value="podlasie">Podlasie</option>
+<option value="pomerania" selected="selected">Pomerania</option>
+<option value="silesia">Silesia</option>
+<option value="swietokrzyskie">Swietokrzyskie</option>
+<option value="warmia-masuria">Warmia-Masuria</option>
+<option value="greater_poland">Greater Poland</option>
+<option value="west_pomerania">West Pomerania</option>
+</select>'''
+ self.assertEqual(f.render('voivodeships', 'pomerania'), out)
+
+ def test_PLCountrySelect(self):
+ f = PLCountySelect()
+ out = u'''<select name="administrativeunit">
+<option value="wroclaw">Wroc\u0142aw</option>
+<option value="jeleniagora">Jelenia G\xf3ra</option>
+<option value="legnica">Legnica</option>
+<option value="boleslawiecki">boles\u0142awiecki</option>
+<option value="dzierzoniowski">dzier\u017coniowski</option>
+<option value="glogowski">g\u0142ogowski</option>
+<option value="gorowski">g\xf3rowski</option>
+<option value="jaworski">jaworski</option>
+<option value="jeleniogorski">jeleniog\xf3rski</option>
+<option value="kamiennogorski">kamiennog\xf3rski</option>
+<option value="klodzki">k\u0142odzki</option>
+<option value="legnicki">legnicki</option>
+<option value="lubanski">luba\u0144ski</option>
+<option value="lubinski">lubi\u0144ski</option>
+<option value="lwowecki">lw\xf3wecki</option>
+<option value="milicki">milicki</option>
+<option value="olesnicki">ole\u015bnicki</option>
+<option value="olawski">o\u0142awski</option>
+<option value="polkowicki">polkowicki</option>
+<option value="strzelinski">strzeli\u0144ski</option>
+<option value="sredzki">\u015bredzki</option>
+<option value="swidnicki">\u015bwidnicki</option>
+<option value="trzebnicki">trzebnicki</option>
+<option value="walbrzyski">wa\u0142brzyski</option>
+<option value="wolowski">wo\u0142owski</option>
+<option value="wroclawski">wroc\u0142awski</option>
+<option value="zabkowicki">z\u0105bkowicki</option>
+<option value="zgorzelecki">zgorzelecki</option>
+<option value="zlotoryjski">z\u0142otoryjski</option>
+<option value="bydgoszcz">Bydgoszcz</option>
+<option value="torun">Toru\u0144</option>
+<option value="wloclawek">W\u0142oc\u0142awek</option>
+<option value="grudziadz">Grudzi\u0105dz</option>
+<option value="aleksandrowski">aleksandrowski</option>
+<option value="brodnicki">brodnicki</option>
+<option value="bydgoski">bydgoski</option>
+<option value="chelminski">che\u0142mi\u0144ski</option>
+<option value="golubsko-dobrzynski">golubsko-dobrzy\u0144ski</option>
+<option value="grudziadzki">grudzi\u0105dzki</option>
+<option value="inowroclawski">inowroc\u0142awski</option>
+<option value="lipnowski">lipnowski</option>
+<option value="mogilenski">mogile\u0144ski</option>
+<option value="nakielski">nakielski</option>
+<option value="radziejowski">radziejowski</option>
+<option value="rypinski">rypi\u0144ski</option>
+<option value="sepolenski">s\u0119pole\u0144ski</option>
+<option value="swiecki">\u015bwiecki</option>
+<option value="torunski">toru\u0144ski</option>
+<option value="tucholski">tucholski</option>
+<option value="wabrzeski">w\u0105brzeski</option>
+<option value="wloclawski">wroc\u0142awski</option>
+<option value="zninski">\u017ani\u0144ski</option>
+<option value="lublin">Lublin</option>
+<option value="biala-podlaska">Bia\u0142a Podlaska</option>
+<option value="chelm">Che\u0142m</option>
+<option value="zamosc">Zamo\u015b\u0107</option>
+<option value="bialski">bialski</option>
+<option value="bilgorajski">bi\u0142gorajski</option>
+<option value="chelmski">che\u0142mski</option>
+<option value="hrubieszowski">hrubieszowski</option>
+<option value="janowski">janowski</option>
+<option value="krasnostawski">krasnostawski</option>
+<option value="krasnicki">kra\u015bnicki</option>
+<option value="lubartowski">lubartowski</option>
+<option value="lubelski">lubelski</option>
+<option value="leczynski">\u0142\u0119czy\u0144ski</option>
+<option value="lukowski">\u0142ukowski</option>
+<option value="opolski">opolski</option>
+<option value="parczewski">parczewski</option>
+<option value="pulawski">pu\u0142awski</option>
+<option value="radzynski">radzy\u0144ski</option>
+<option value="rycki">rycki</option>
+<option value="swidnicki">\u015bwidnicki</option>
+<option value="tomaszowski">tomaszowski</option>
+<option value="wlodawski">w\u0142odawski</option>
+<option value="zamojski">zamojski</option>
+<option value="gorzow-wielkopolski">Gorz\xf3w Wielkopolski</option>
+<option value="zielona-gora">Zielona G\xf3ra</option>
+<option value="gorzowski">gorzowski</option>
+<option value="krosnienski">kro\u015bnie\u0144ski</option>
+<option value="miedzyrzecki">mi\u0119dzyrzecki</option>
+<option value="nowosolski">nowosolski</option>
+<option value="slubicki">s\u0142ubicki</option>
+<option value="strzelecko-drezdenecki">strzelecko-drezdenecki</option>
+<option value="sulecinski">sule\u0144ci\u0144ski</option>
+<option value="swiebodzinski">\u015bwiebodzi\u0144ski</option>
+<option value="wschowski">wschowski</option>
+<option value="zielonogorski">zielonog\xf3rski</option>
+<option value="zaganski">\u017caga\u0144ski</option>
+<option value="zarski">\u017carski</option>
+<option value="lodz">\u0141\xf3d\u017a</option>
+<option value="piotrkow-trybunalski">Piotrk\xf3w Trybunalski</option>
+<option value="skierniewice">Skierniewice</option>
+<option value="belchatowski">be\u0142chatowski</option>
+<option value="brzezinski">brzezi\u0144ski</option>
+<option value="kutnowski">kutnowski</option>
+<option value="laski">\u0142aski</option>
+<option value="leczycki">\u0142\u0119czycki</option>
+<option value="lowicki">\u0142owicki</option>
+<option value="lodzki wschodni">\u0142\xf3dzki wschodni</option>
+<option value="opoczynski">opoczy\u0144ski</option>
+<option value="pabianicki">pabianicki</option>
+<option value="pajeczanski">paj\u0119cza\u0144ski</option>
+<option value="piotrkowski">piotrkowski</option>
+<option value="poddebicki">podd\u0119bicki</option>
+<option value="radomszczanski">radomszcza\u0144ski</option>
+<option value="rawski">rawski</option>
+<option value="sieradzki">sieradzki</option>
+<option value="skierniewicki">skierniewicki</option>
+<option value="tomaszowski">tomaszowski</option>
+<option value="wielunski">wielu\u0144ski</option>
+<option value="wieruszowski">wieruszowski</option>
+<option value="zdunskowolski">zdu\u0144skowolski</option>
+<option value="zgierski">zgierski</option>
+<option value="krakow">Krak\xf3w</option>
+<option value="tarnow">Tarn\xf3w</option>
+<option value="nowy-sacz">Nowy S\u0105cz</option>
+<option value="bochenski">boche\u0144ski</option>
+<option value="brzeski">brzeski</option>
+<option value="chrzanowski">chrzanowski</option>
+<option value="dabrowski">d\u0105browski</option>
+<option value="gorlicki">gorlicki</option>
+<option value="krakowski">krakowski</option>
+<option value="limanowski">limanowski</option>
+<option value="miechowski">miechowski</option>
+<option value="myslenicki">my\u015blenicki</option>
+<option value="nowosadecki">nowos\u0105decki</option>
+<option value="nowotarski">nowotarski</option>
+<option value="olkuski">olkuski</option>
+<option value="oswiecimski">o\u015bwi\u0119cimski</option>
+<option value="proszowicki">proszowicki</option>
+<option value="suski">suski</option>
+<option value="tarnowski">tarnowski</option>
+<option value="tatrzanski">tatrza\u0144ski</option>
+<option value="wadowicki">wadowicki</option>
+<option value="wielicki">wielicki</option>
+<option value="warszawa">Warszawa</option>
+<option value="ostroleka">Ostro\u0142\u0119ka</option>
+<option value="plock">P\u0142ock</option>
+<option value="radom">Radom</option>
+<option value="siedlce">Siedlce</option>
+<option value="bialobrzeski">bia\u0142obrzeski</option>
+<option value="ciechanowski">ciechanowski</option>
+<option value="garwolinski">garwoli\u0144ski</option>
+<option value="gostyninski">gostyni\u0144ski</option>
+<option value="grodziski">grodziski</option>
+<option value="grojecki">gr\xf3jecki</option>
+<option value="kozienicki">kozenicki</option>
+<option value="legionowski">legionowski</option>
+<option value="lipski">lipski</option>
+<option value="losicki">\u0142osicki</option>
+<option value="makowski">makowski</option>
+<option value="minski">mi\u0144ski</option>
+<option value="mlawski">m\u0142awski</option>
+<option value="nowodworski">nowodworski</option>
+<option value="ostrolecki">ostro\u0142\u0119cki</option>
+<option value="ostrowski">ostrowski</option>
+<option value="otwocki">otwocki</option>
+<option value="piaseczynski">piaseczy\u0144ski</option>
+<option value="plocki">p\u0142ocki</option>
+<option value="plonski">p\u0142o\u0144ski</option>
+<option value="pruszkowski">pruszkowski</option>
+<option value="przasnyski">przasnyski</option>
+<option value="przysuski">przysuski</option>
+<option value="pultuski">pu\u0142tuski</option>
+<option value="radomski">radomski</option>
+<option value="siedlecki">siedlecki</option>
+<option value="sierpecki">sierpecki</option>
+<option value="sochaczewski">sochaczewski</option>
+<option value="sokolowski">soko\u0142owski</option>
+<option value="szydlowiecki">szyd\u0142owiecki</option>
+<option value="warszawski-zachodni">warszawski zachodni</option>
+<option value="wegrowski">w\u0119growski</option>
+<option value="wolominski">wo\u0142omi\u0144ski</option>
+<option value="wyszkowski">wyszkowski</option>
+<option value="zwolenski">zwole\u0144ski</option>
+<option value="zurominski">\u017curomi\u0144ski</option>
+<option value="zyrardowski">\u017cyrardowski</option>
+<option value="opole">Opole</option>
+<option value="brzeski">brzeski</option>
+<option value="glubczycki">g\u0142ubczyski</option>
+<option value="kedzierzynsko-kozielski">k\u0119dzierzy\u0144ski-kozielski</option>
+<option value="kluczborski">kluczborski</option>
+<option value="krapkowicki">krapkowicki</option>
+<option value="namyslowski">namys\u0142owski</option>
+<option value="nyski">nyski</option>
+<option value="oleski">oleski</option>
+<option value="opolski">opolski</option>
+<option value="prudnicki">prudnicki</option>
+<option value="strzelecki">strzelecki</option>
+<option value="rzeszow">Rzesz\xf3w</option>
+<option value="krosno">Krosno</option>
+<option value="przemysl">Przemy\u015bl</option>
+<option value="tarnobrzeg">Tarnobrzeg</option>
+<option value="bieszczadzki">bieszczadzki</option>
+<option value="brzozowski">brzozowski</option>
+<option value="debicki">d\u0119bicki</option>
+<option value="jaroslawski">jaros\u0142awski</option>
+<option value="jasielski">jasielski</option>
+<option value="kolbuszowski">kolbuszowski</option>
+<option value="krosnienski">kro\u015bnie\u0144ski</option>
+<option value="leski">leski</option>
+<option value="lezajski">le\u017cajski</option>
+<option value="lubaczowski">lubaczowski</option>
+<option value="lancucki">\u0142a\u0144cucki</option>
+<option value="mielecki">mielecki</option>
+<option value="nizanski">ni\u017ca\u0144ski</option>
+<option value="przemyski">przemyski</option>
+<option value="przeworski">przeworski</option>
+<option value="ropczycko-sedziszowski">ropczycko-s\u0119dziszowski</option>
+<option value="rzeszowski">rzeszowski</option>
+<option value="sanocki">sanocki</option>
+<option value="stalowowolski">stalowowolski</option>
+<option value="strzyzowski">strzy\u017cowski</option>
+<option value="tarnobrzeski">tarnobrzeski</option>
+<option value="bialystok">Bia\u0142ystok</option>
+<option value="lomza">\u0141om\u017ca</option>
+<option value="suwalki">Suwa\u0142ki</option>
+<option value="augustowski">augustowski</option>
+<option value="bialostocki">bia\u0142ostocki</option>
+<option value="bielski">bielski</option>
+<option value="grajewski">grajewski</option>
+<option value="hajnowski">hajnowski</option>
+<option value="kolnenski">kolne\u0144ski</option>
+<option value="\u0142omzynski">\u0142om\u017cy\u0144ski</option>
+<option value="moniecki">moniecki</option>
+<option value="sejnenski">sejne\u0144ski</option>
+<option value="siemiatycki">siematycki</option>
+<option value="sokolski">sok\xf3lski</option>
+<option value="suwalski">suwalski</option>
+<option value="wysokomazowiecki">wysokomazowiecki</option>
+<option value="zambrowski">zambrowski</option>
+<option value="gdansk">Gda\u0144sk</option>
+<option value="gdynia">Gdynia</option>
+<option value="slupsk">S\u0142upsk</option>
+<option value="sopot">Sopot</option>
+<option value="bytowski">bytowski</option>
+<option value="chojnicki">chojnicki</option>
+<option value="czluchowski">cz\u0142uchowski</option>
+<option value="kartuski">kartuski</option>
+<option value="koscierski">ko\u015bcierski</option>
+<option value="kwidzynski">kwidzy\u0144ski</option>
+<option value="leborski">l\u0119borski</option>
+<option value="malborski">malborski</option>
+<option value="nowodworski">nowodworski</option>
+<option value="gdanski">gda\u0144ski</option>
+<option value="pucki">pucki</option>
+<option value="slupski">s\u0142upski</option>
+<option value="starogardzki">starogardzki</option>
+<option value="sztumski">sztumski</option>
+<option value="tczewski">tczewski</option>
+<option value="wejherowski">wejcherowski</option>
+<option value="katowice" selected="selected">Katowice</option>
+<option value="bielsko-biala">Bielsko-Bia\u0142a</option>
+<option value="bytom">Bytom</option>
+<option value="chorzow">Chorz\xf3w</option>
+<option value="czestochowa">Cz\u0119stochowa</option>
+<option value="dabrowa-gornicza">D\u0105browa G\xf3rnicza</option>
+<option value="gliwice">Gliwice</option>
+<option value="jastrzebie-zdroj">Jastrz\u0119bie Zdr\xf3j</option>
+<option value="jaworzno">Jaworzno</option>
+<option value="myslowice">Mys\u0142owice</option>
+<option value="piekary-slaskie">Piekary \u015al\u0105skie</option>
+<option value="ruda-slaska">Ruda \u015al\u0105ska</option>
+<option value="rybnik">Rybnik</option>
+<option value="siemianowice-slaskie">Siemianowice \u015al\u0105skie</option>
+<option value="sosnowiec">Sosnowiec</option>
+<option value="swietochlowice">\u015awi\u0119toch\u0142owice</option>
+<option value="tychy">Tychy</option>
+<option value="zabrze">Zabrze</option>
+<option value="zory">\u017bory</option>
+<option value="bedzinski">b\u0119dzi\u0144ski</option>
+<option value="bielski">bielski</option>
+<option value="bierunsko-ledzinski">bieru\u0144sko-l\u0119dzi\u0144ski</option>
+<option value="cieszynski">cieszy\u0144ski</option>
+<option value="czestochowski">cz\u0119stochowski</option>
+<option value="gliwicki">gliwicki</option>
+<option value="klobucki">k\u0142obucki</option>
+<option value="lubliniecki">lubliniecki</option>
+<option value="mikolowski">miko\u0142owski</option>
+<option value="myszkowski">myszkowski</option>
+<option value="pszczynski">pszczy\u0144ski</option>
+<option value="raciborski">raciborski</option>
+<option value="rybnicki">rybnicki</option>
+<option value="tarnogorski">tarnog\xf3rski</option>
+<option value="wodzislawski">wodzis\u0142awski</option>
+<option value="zawiercianski">zawiercia\u0144ski</option>
+<option value="zywiecki">\u017cywiecki</option>
+<option value="kielce">Kielce</option>
+<option value="buski">buski</option>
+<option value="jedrzejowski">j\u0119drzejowski</option>
+<option value="kazimierski">kazimierski</option>
+<option value="kielecki">kielecki</option>
+<option value="konecki">konecki</option>
+<option value="opatowski">opatowski</option>
+<option value="ostrowiecki">ostrowiecki</option>
+<option value="pinczowski">pi\u0144czowski</option>
+<option value="sandomierski">sandomierski</option>
+<option value="skarzyski">skar\u017cyski</option>
+<option value="starachowicki">starachowicki</option>
+<option value="staszowski">staszowski</option>
+<option value="wloszczowski">w\u0142oszczowski</option>
+<option value="olsztyn">Olsztyn</option>
+<option value="elblag">Elbl\u0105g</option>
+<option value="bartoszycki">bartoszycki</option>
+<option value="braniewski">braniewski</option>
+<option value="dzialdowski">dzia\u0142dowski</option>
+<option value="elblaski">elbl\u0105ski</option>
+<option value="elcki">e\u0142cki</option>
+<option value="gizycki">gi\u017cycki</option>
+<option value="goldapski">go\u0142dapski</option>
+<option value="ilawski">i\u0142awski</option>
+<option value="ketrzynski">k\u0119trzy\u0144ski</option>
+<option value="lidzbarski">lidzbarski</option>
+<option value="mragowski">mr\u0105gowski</option>
+<option value="nidzicki">nidzicki</option>
+<option value="nowomiejski">nowomiejski</option>
+<option value="olecki">olecki</option>
+<option value="olsztynski">olszty\u0144ski</option>
+<option value="ostrodzki">ostr\xf3dzki</option>
+<option value="piski">piski</option>
+<option value="szczycienski">szczycie\u0144ski</option>
+<option value="wegorzewski">w\u0119gorzewski</option>
+<option value="poznan">Pozna\u0144</option>
+<option value="kalisz">Kalisz</option>
+<option value="konin">Konin</option>
+<option value="leszno">Leszno</option>
+<option value="chodzieski">chodziejski</option>
+<option value="czarnkowsko-trzcianecki">czarnkowsko-trzcianecki</option>
+<option value="gnieznienski">gnie\u017anie\u0144ski</option>
+<option value="gostynski">gosty\u0144ski</option>
+<option value="grodziski">grodziski</option>
+<option value="jarocinski">jaroci\u0144ski</option>
+<option value="kaliski">kaliski</option>
+<option value="kepinski">k\u0119pi\u0144ski</option>
+<option value="kolski">kolski</option>
+<option value="koninski">koni\u0144ski</option>
+<option value="koscianski">ko\u015bcia\u0144ski</option>
+<option value="krotoszynski">krotoszy\u0144ski</option>
+<option value="leszczynski">leszczy\u0144ski</option>
+<option value="miedzychodzki">mi\u0119dzychodzki</option>
+<option value="nowotomyski">nowotomyski</option>
+<option value="obornicki">obornicki</option>
+<option value="ostrowski">ostrowski</option>
+<option value="ostrzeszowski">ostrzeszowski</option>
+<option value="pilski">pilski</option>
+<option value="pleszewski">pleszewski</option>
+<option value="poznanski">pozna\u0144ski</option>
+<option value="rawicki">rawicki</option>
+<option value="slupecki">s\u0142upecki</option>
+<option value="szamotulski">szamotulski</option>
+<option value="sredzki">\u015bredzki</option>
+<option value="sremski">\u015bremski</option>
+<option value="turecki">turecki</option>
+<option value="wagrowiecki">w\u0105growiecki</option>
+<option value="wolsztynski">wolszty\u0144ski</option>
+<option value="wrzesinski">wrzesi\u0144ski</option>
+<option value="zlotowski">z\u0142otowski</option>
+<option value="bialogardzki">bia\u0142ogardzki</option>
+<option value="choszczenski">choszcze\u0144ski</option>
+<option value="drawski">drawski</option>
+<option value="goleniowski">goleniowski</option>
+<option value="gryficki">gryficki</option>
+<option value="gryfinski">gryfi\u0144ski</option>
+<option value="kamienski">kamie\u0144ski</option>
+<option value="kolobrzeski">ko\u0142obrzeski</option>
+<option value="koszalinski">koszali\u0144ski</option>
+<option value="lobeski">\u0142obeski</option>
+<option value="mysliborski">my\u015bliborski</option>
+<option value="policki">policki</option>
+<option value="pyrzycki">pyrzycki</option>
+<option value="slawienski">s\u0142awie\u0144ski</option>
+<option value="stargardzki">stargardzki</option>
+<option value="szczecinecki">szczecinecki</option>
+<option value="swidwinski">\u015bwidwi\u0144ski</option>
+<option value="walecki">wa\u0142ecki</option>
+</select>'''
+ self.assertEqual(f.render('administrativeunit', 'katowice'), out)
+
+ def test_PLPostalCodeField(self):
+ error_format = [u'Enter a postal code in the format XX-XXX.']
+ valid = {
+ '41-403': '41-403',
+ }
+ invalid = {
+ '43--434': error_format,
+ }
+ self.assertFieldOutput(PLPostalCodeField, valid, invalid)
+
+ def test_PLNIPField(self):
+ error_format = [u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.']
+ error_checksum = [u'Wrong checksum for the Tax Number (NIP).']
+ valid = {
+ '64-62-414-124': '6462414124',
+ '646-241-41-24': '6462414124',
+ }
+ invalid = {
+ '43-343-234-323': error_format,
+ '646-241-41-23': error_checksum,
+ }
+ self.assertFieldOutput(PLNIPField, valid, invalid)
+
+ def test_PLPESELField(self):
+ error_checksum = [u'Wrong checksum for the National Identification Number.']
+ error_format = [u'National Identification Number consists of 11 digits.']
+ valid = {
+ '80071610614': '80071610614',
+ }
+ invalid = {
+ '80071610610': error_checksum,
+ '80': error_format,
+ '800716106AA': error_format,
+ }
+ self.assertFieldOutput(PLPESELField, valid, invalid)
+
+ def test_PLREGONField(self):
+ error_checksum = [u'Wrong checksum for the National Business Register Number (REGON).']
+ error_format = [u'National Business Register Number (REGON) consists of 9 or 14 digits.']
+ valid = {
+ '12345678512347': '12345678512347',
+ '590096454': '590096454',
+ }
+ invalid = {
+ '123456784': error_checksum,
+ '12345678412342': error_checksum,
+ '590096453': error_checksum,
+ '590096': error_format,
+ }
+ self.assertFieldOutput(PLREGONField, valid, invalid)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/pt.py b/parts/django/tests/regressiontests/forms/localflavor/pt.py
new file mode 100644
index 0000000..b8d784a
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/pt.py
@@ -0,0 +1,32 @@
+from django.contrib.localflavor.pt.forms import PTZipCodeField, PTPhoneNumberField
+
+from utils import LocalFlavorTestCase
+
+
+class PTLocalFlavorTests(LocalFlavorTestCase):
+ def test_PTZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXX-XXX.']
+ valid = {
+ '3030-034': '3030-034',
+ '1003456': '1003-456',
+ }
+ invalid = {
+ '2A200': error_format,
+ '980001': error_format,
+ }
+ self.assertFieldOutput(PTZipCodeField, valid, invalid)
+
+ def test_PTPhoneNumberField(self):
+ error_format = [u'Phone numbers must have 9 digits, or start by + or 00.']
+ valid = {
+ '917845189': '917845189',
+ '91 784 5189': '917845189',
+ '91 784 5189': '917845189',
+ '+351 91 111': '+35191111',
+ '00351873': '00351873',
+ }
+ invalid = {
+ '91 784 51 8': error_format,
+ '091 456 987 1': error_format,
+ }
+ self.assertFieldOutput(PTPhoneNumberField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/ro.py b/parts/django/tests/regressiontests/forms/localflavor/ro.py
new file mode 100644
index 0000000..c3dd403
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/ro.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+from django.contrib.localflavor.ro.forms import (ROCIFField, ROCNPField,
+ ROCountyField, ROCountySelect, ROIBANField, ROPhoneNumberField,
+ ROPostalCodeField)
+
+from utils import LocalFlavorTestCase
+
+
+class ROLocalFlavorTests(LocalFlavorTestCase):
+ def test_ROCountySelect(self):
+ f = ROCountySelect()
+ out = u'''<select name="county">
+<option value="AB">Alba</option>
+<option value="AR">Arad</option>
+<option value="AG">Arge\u015f</option>
+<option value="BC">Bac\u0103u</option>
+<option value="BH">Bihor</option>
+<option value="BN">Bistri\u0163a-N\u0103s\u0103ud</option>
+<option value="BT">Boto\u015fani</option>
+<option value="BV">Bra\u015fov</option>
+<option value="BR">Br\u0103ila</option>
+<option value="B">Bucure\u015fti</option>
+<option value="BZ">Buz\u0103u</option>
+<option value="CS">Cara\u015f-Severin</option>
+<option value="CL">C\u0103l\u0103ra\u015fi</option>
+<option value="CJ" selected="selected">Cluj</option>
+<option value="CT">Constan\u0163a</option>
+<option value="CV">Covasna</option>
+<option value="DB">D\xe2mbovi\u0163a</option>
+<option value="DJ">Dolj</option>
+<option value="GL">Gala\u0163i</option>
+<option value="GR">Giurgiu</option>
+<option value="GJ">Gorj</option>
+<option value="HR">Harghita</option>
+<option value="HD">Hunedoara</option>
+<option value="IL">Ialomi\u0163a</option>
+<option value="IS">Ia\u015fi</option>
+<option value="IF">Ilfov</option>
+<option value="MM">Maramure\u015f</option>
+<option value="MH">Mehedin\u0163i</option>
+<option value="MS">Mure\u015f</option>
+<option value="NT">Neam\u0163</option>
+<option value="OT">Olt</option>
+<option value="PH">Prahova</option>
+<option value="SM">Satu Mare</option>
+<option value="SJ">S\u0103laj</option>
+<option value="SB">Sibiu</option>
+<option value="SV">Suceava</option>
+<option value="TR">Teleorman</option>
+<option value="TM">Timi\u015f</option>
+<option value="TL">Tulcea</option>
+<option value="VS">Vaslui</option>
+<option value="VL">V\xe2lcea</option>
+<option value="VN">Vrancea</option>
+</select>'''
+ self.assertEqual(f.render('county', 'CJ'), out)
+
+ def test_ROCIFField(self):
+ error_invalid = [u'Enter a valid CIF.']
+ error_atmost = [u'Ensure this value has at most 10 characters (it has 11).']
+ error_atleast = [u'Ensure this value has at least 2 characters (it has 1).']
+ valid = {
+ '21694681': u'21694681',
+ 'RO21694681': u'21694681',
+ }
+ invalid = {
+ '21694680': error_invalid,
+ '21694680000': error_atmost,
+ '0': error_atleast + error_invalid,
+ }
+ self.assertFieldOutput(ROCIFField, valid, invalid)
+
+ def test_ROCNPField(self):
+ error_invalid = [u'Enter a valid CNP.']
+ error_atleast = [u'Ensure this value has at least 13 characters (it has 10).']
+ error_atmost = [u'Ensure this value has at most 13 characters (it has 14).']
+ valid = {
+ '1981211204489': '1981211204489',
+ }
+ invalid = {
+ '1981211204487': error_invalid,
+ '1981232204489': error_invalid,
+ '9981211204489': error_invalid,
+ '9981211209': error_atleast + error_invalid,
+ '19812112044891': error_atmost,
+ }
+ self.assertFieldOutput(ROCNPField, valid, invalid)
+
+ def test_ROCountyField(self):
+ error_format = [u'Enter a Romanian county code or name.']
+ valid = {
+ 'CJ': 'CJ',
+ 'cj': 'CJ',
+ u'Argeş': 'AG',
+ u'argeş': 'AG',
+ }
+ invalid = {
+ 'Arges': error_format,
+ }
+ self.assertFieldOutput(ROCountyField, valid, invalid)
+
+ def test_ROIBANField(self):
+ error_invalid = [u'Enter a valid IBAN in ROXX-XXXX-XXXX-XXXX-XXXX-XXXX format']
+ error_atleast = [u'Ensure this value has at least 24 characters (it has 23).']
+ valid = {
+ 'RO56RZBR0000060003291177': 'RO56RZBR0000060003291177',
+ 'RO56-RZBR-0000-0600-0329-1177': 'RO56RZBR0000060003291177',
+ }
+ invalid = {
+ 'RO56RZBR0000060003291176': error_invalid,
+ 'AT61 1904 3002 3457 3201': error_invalid,
+ 'RO56RZBR000006000329117': error_atleast + error_invalid,
+ }
+ self.assertFieldOutput(ROIBANField, valid, invalid)
+
+ def test_ROPhoneNumberField(self):
+ error_format = [u'Phone numbers must be in XXXX-XXXXXX format.']
+ error_atleast = [u'Ensure this value has at least 10 characters (it has 9).']
+ valid = {
+ '0264485936': '0264485936',
+ '(0264)-485936': '0264485936',
+ }
+ invalid = {
+ '02644859368': error_format,
+ '026448593': error_atleast + error_format
+ ,
+ }
+ self.assertFieldOutput(ROPhoneNumberField, valid, invalid)
+
+ def test_ROPostalCodeField(self):
+ error_atleast = [u'Ensure this value has at least 6 characters (it has 5).']
+ error_atmost = [u'Ensure this value has at most 6 characters (it has 7).']
+ error_invalid = [u'Enter a valid postal code in the format XXXXXX']
+
+ valid = {
+ '400473': '400473',
+ }
+ invalid = {
+ '40047': error_atleast + error_invalid,
+ '4004731': error_atmost + error_invalid,
+ }
+ self.assertFieldOutput(ROPostalCodeField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/se.py b/parts/django/tests/regressiontests/forms/localflavor/se.py
new file mode 100644
index 0000000..316a612
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/se.py
@@ -0,0 +1,331 @@
+# -*- coding: utf-8 -*-
+# Tests for the contrib/localflavor/se form fields.
+
+tests = r"""
+# Monkey-patch datetime.date
+>>> import datetime
+>>> class MockDate(datetime.date):
+... def today(cls):
+... return datetime.date(2008, 5, 14)
+... today = classmethod(today)
+...
+>>> olddate = datetime.date
+>>> datetime.date = MockDate
+>>> datetime.date.today() == olddate(2008, 5, 14)
+True
+
+# SECountySelect #####################################################
+>>> from django.contrib.localflavor.se.forms import SECountySelect
+
+>>> w = SECountySelect()
+>>> w.render('swedish_county', 'E')
+u'<select name="swedish_county">\n<option value="AB">Stockholm</option>\n<option value="AC">V\xe4sterbotten</option>\n<option value="BD">Norrbotten</option>\n<option value="C">Uppsala</option>\n<option value="D">S\xf6dermanland</option>\n<option value="E" selected="selected">\xd6sterg\xf6tland</option>\n<option value="F">J\xf6nk\xf6ping</option>\n<option value="G">Kronoberg</option>\n<option value="H">Kalmar</option>\n<option value="I">Gotland</option>\n<option value="K">Blekinge</option>\n<option value="M">Sk\xe5ne</option>\n<option value="N">Halland</option>\n<option value="O">V\xe4stra G\xf6taland</option>\n<option value="S">V\xe4rmland</option>\n<option value="T">\xd6rebro</option>\n<option value="U">V\xe4stmanland</option>\n<option value="W">Dalarna</option>\n<option value="X">G\xe4vleborg</option>\n<option value="Y">V\xe4sternorrland</option>\n<option value="Z">J\xe4mtland</option>\n</select>'
+
+# SEOrganisationNumberField #######################################
+
+>>> from django.contrib.localflavor.se.forms import SEOrganisationNumberField
+
+>>> f = SEOrganisationNumberField()
+
+# Ordinary personal identity numbers for sole proprietors
+# The same rules as for SEPersonalIdentityField applies here
+>>> f.clean('870512-1989')
+u'198705121989'
+>>> f.clean('19870512-1989')
+u'198705121989'
+>>> f.clean('870512-2128')
+u'198705122128'
+>>> f.clean('081015-6315')
+u'190810156315'
+>>> f.clean('081015+6315')
+u'180810156315'
+>>> f.clean('0810156315')
+u'190810156315'
+
+>>> f.clean('081015 6315')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+>>> f.clean('950231-4496')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+>>> f.clean('6914104499')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+>>> f.clean('950d314496')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+>>> f.clean('invalid!!!')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+>>> f.clean('870514-1111')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+
+
+# Empty values
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+# Co-ordination number checking
+# Co-ordination numbers are not valid organisation numbers
+>>> f.clean('870574-1315')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+
+>>> f.clean('870573-1311')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+
+# Test some different organisation numbers
+>>> f.clean('556074-7569') # IKEA Linköping
+u'5560747569'
+
+>>> f.clean('556074-3089') # Volvo Personvagnar
+u'5560743089'
+
+>>> f.clean('822001-5476') # LJS (organisation)
+u'8220015476'
+
+>>> f.clean('8220015476') # LJS (organisation)
+u'8220015476'
+
+>>> f.clean('2120000449') # Katedralskolan Linköping (school)
+u'2120000449'
+
+# Faux organisation number, which tests that the checksum can be 0
+>>> f.clean('232518-5060')
+u'2325185060'
+
+>>> f.clean('556074+3089') # Volvo Personvagnar, bad format
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+
+
+# Invalid checksum
+>>> f.clean('2120000441')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+
+# Valid checksum but invalid organisation type
+f.clean('1120000441')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish organisation number.']
+
+# Empty values with required=False
+>>> f = SEOrganisationNumberField(required=False)
+
+>>> f.clean(None)
+u''
+
+>>> f.clean('')
+u''
+
+
+# SEPersonalIdentityNumberField #######################################
+
+>>> from django.contrib.localflavor.se.forms import SEPersonalIdentityNumberField
+
+>>> f = SEPersonalIdentityNumberField()
+
+# Valid id numbers
+>>> f.clean('870512-1989')
+u'198705121989'
+
+>>> f.clean('870512-2128')
+u'198705122128'
+
+>>> f.clean('19870512-1989')
+u'198705121989'
+
+>>> f.clean('198705121989')
+u'198705121989'
+
+>>> f.clean('081015-6315')
+u'190810156315'
+
+>>> f.clean('0810156315')
+u'190810156315'
+
+# This is a "special-case" in the checksum calculation,
+# where the sum is divisible by 10 (the checksum digit == 0)
+>>> f.clean('8705141060')
+u'198705141060'
+
+# + means that the person is older than 100 years
+>>> f.clean('081015+6315')
+u'180810156315'
+
+# Bogus values
+>>> f.clean('081015 6315')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+>>> f.clean('950d314496')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+>>> f.clean('invalid!!!')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+
+# Invalid dates
+
+# February 31st does not exist
+>>> f.clean('950231-4496')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+# Month 14 does not exist
+>>> f.clean('6914104499')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+# There are no Swedish personal id numbers where year < 1800
+>>> f.clean('17430309-7135')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+# Invalid checksum
+>>> f.clean('870514-1111')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+# Empty values
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+# Co-ordination number checking
+>>> f.clean('870574-1315')
+u'198705741315'
+
+>>> f.clean('870574+1315')
+u'188705741315'
+
+>>> f.clean('198705741315')
+u'198705741315'
+
+# Co-ordination number with bad checksum
+>>> f.clean('870573-1311')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+
+# Check valid co-ordination numbers, that should not be accepted
+# because of coordination_number=False
+>>> f = SEPersonalIdentityNumberField(coordination_number=False)
+
+>>> f.clean('870574-1315')
+Traceback (most recent call last):
+...
+ValidationError: [u'Co-ordination numbers are not allowed.']
+
+>>> f.clean('870574+1315')
+Traceback (most recent call last):
+...
+ValidationError: [u'Co-ordination numbers are not allowed.']
+
+>>> f.clean('8705741315')
+Traceback (most recent call last):
+...
+ValidationError: [u'Co-ordination numbers are not allowed.']
+
+# Invalid co-ordination numbers should be treated as invalid, and not
+# as co-ordination numbers
+>>> f.clean('870573-1311')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swedish personal identity number.']
+
+# Empty values with required=False
+>>> f = SEPersonalIdentityNumberField(required=False)
+
+>>> f.clean(None)
+u''
+
+>>> f.clean('')
+u''
+
+# SEPostalCodeField ###############################################
+>>> from django.contrib.localflavor.se.forms import SEPostalCodeField
+>>> f = SEPostalCodeField()
+>>>
+Postal codes can have spaces
+>>> f.clean('589 37')
+u'58937'
+
+... but the dont have to
+>>> f.clean('58937')
+u'58937'
+>>> f.clean('abcasfassadf')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
+
+# Only one space is allowed for separation
+>>> f.clean('589 37')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
+
+# The postal code must not start with 0
+>>> f.clean('01234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
+
+# Empty values
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+# Empty values, required=False
+>>> f = SEPostalCodeField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
+
+# Revert the monkey patching
+>>> datetime.date = olddate
+
+"""
diff --git a/parts/django/tests/regressiontests/forms/localflavor/sk.py b/parts/django/tests/regressiontests/forms/localflavor/sk.py
new file mode 100644
index 0000000..a5bed6f
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/sk.py
@@ -0,0 +1,116 @@
+from django.contrib.localflavor.sk.forms import (SKRegionSelect,
+ SKPostalCodeField, SKDistrictSelect)
+
+from utils import LocalFlavorTestCase
+
+
+class SKLocalFlavorTests(LocalFlavorTestCase):
+ def test_SKRegionSelect(self):
+ f = SKRegionSelect()
+ out = u'''<select name="regions">
+<option value="BB">Banska Bystrica region</option>
+<option value="BA">Bratislava region</option>
+<option value="KE">Kosice region</option>
+<option value="NR">Nitra region</option>
+<option value="PO">Presov region</option>
+<option value="TN">Trencin region</option>
+<option value="TT" selected="selected">Trnava region</option>
+<option value="ZA">Zilina region</option>
+</select>'''
+ self.assertEqual(f.render('regions', 'TT'), out)
+
+ def test_SKDistrictSelect(self):
+ f = SKDistrictSelect()
+ out = u'''<select name="Districts">
+<option value="BB">Banska Bystrica</option>
+<option value="BS">Banska Stiavnica</option>
+<option value="BJ">Bardejov</option>
+<option value="BN">Banovce nad Bebravou</option>
+<option value="BR">Brezno</option>
+<option value="BA1">Bratislava I</option>
+<option value="BA2">Bratislava II</option>
+<option value="BA3">Bratislava III</option>
+<option value="BA4">Bratislava IV</option>
+<option value="BA5">Bratislava V</option>
+<option value="BY">Bytca</option>
+<option value="CA">Cadca</option>
+<option value="DT">Detva</option>
+<option value="DK">Dolny Kubin</option>
+<option value="DS">Dunajska Streda</option>
+<option value="GA">Galanta</option>
+<option value="GL">Gelnica</option>
+<option value="HC">Hlohovec</option>
+<option value="HE">Humenne</option>
+<option value="IL">Ilava</option>
+<option value="KK">Kezmarok</option>
+<option value="KN">Komarno</option>
+<option value="KE1">Kosice I</option>
+<option value="KE2">Kosice II</option>
+<option value="KE3">Kosice III</option>
+<option value="KE4">Kosice IV</option>
+<option value="KEO">Kosice - okolie</option>
+<option value="KA">Krupina</option>
+<option value="KM">Kysucke Nove Mesto</option>
+<option value="LV">Levice</option>
+<option value="LE">Levoca</option>
+<option value="LM">Liptovsky Mikulas</option>
+<option value="LC">Lucenec</option>
+<option value="MA">Malacky</option>
+<option value="MT">Martin</option>
+<option value="ML">Medzilaborce</option>
+<option value="MI">Michalovce</option>
+<option value="MY">Myjava</option>
+<option value="NO">Namestovo</option>
+<option value="NR">Nitra</option>
+<option value="NM">Nove Mesto nad Vahom</option>
+<option value="NZ">Nove Zamky</option>
+<option value="PE">Partizanske</option>
+<option value="PK">Pezinok</option>
+<option value="PN">Piestany</option>
+<option value="PT">Poltar</option>
+<option value="PP">Poprad</option>
+<option value="PB">Povazska Bystrica</option>
+<option value="PO">Presov</option>
+<option value="PD">Prievidza</option>
+<option value="PU">Puchov</option>
+<option value="RA">Revuca</option>
+<option value="RS">Rimavska Sobota</option>
+<option value="RV">Roznava</option>
+<option value="RK" selected="selected">Ruzomberok</option>
+<option value="SB">Sabinov</option>
+<option value="SC">Senec</option>
+<option value="SE">Senica</option>
+<option value="SI">Skalica</option>
+<option value="SV">Snina</option>
+<option value="SO">Sobrance</option>
+<option value="SN">Spisska Nova Ves</option>
+<option value="SL">Stara Lubovna</option>
+<option value="SP">Stropkov</option>
+<option value="SK">Svidnik</option>
+<option value="SA">Sala</option>
+<option value="TO">Topolcany</option>
+<option value="TV">Trebisov</option>
+<option value="TN">Trencin</option>
+<option value="TT">Trnava</option>
+<option value="TR">Turcianske Teplice</option>
+<option value="TS">Tvrdosin</option>
+<option value="VK">Velky Krtis</option>
+<option value="VT">Vranov nad Toplou</option>
+<option value="ZM">Zlate Moravce</option>
+<option value="ZV">Zvolen</option>
+<option value="ZC">Zarnovica</option>
+<option value="ZH">Ziar nad Hronom</option>
+<option value="ZA">Zilina</option>
+</select>'''
+ self.assertEqual(f.render('Districts', 'RK'), out)
+
+ def test_SKPostalCodeField(self):
+ error_format = [u'Enter a postal code in the format XXXXX or XXX XX.']
+ valid = {
+ '91909': '91909',
+ '917 01': '91701',
+ }
+ invalid = {
+ '84545x': error_format,
+ }
+ self.assertFieldOutput(SKPostalCodeField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/uk.py b/parts/django/tests/regressiontests/forms/localflavor/uk.py
new file mode 100644
index 0000000..6fd536f
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/uk.py
@@ -0,0 +1,30 @@
+from django.contrib.localflavor.uk.forms import UKPostcodeField
+
+from utils import LocalFlavorTestCase
+
+
+class UKLocalFlavorTests(LocalFlavorTestCase):
+ def test_UKPostcodeField(self):
+ error_invalid = [u'Enter a valid postcode.']
+ valid = {
+ 'BT32 4PX': 'BT32 4PX',
+ 'GIR 0AA': 'GIR 0AA',
+ 'BT324PX': 'BT32 4PX',
+ ' so11aa ': 'SO1 1AA',
+ ' so1 1aa ': 'SO1 1AA',
+ 'G2 3wt': 'G2 3WT',
+ 'EC1A 1BB': 'EC1A 1BB',
+ 'Ec1a1BB': 'EC1A 1BB',
+ }
+ invalid = {
+ '1NV 4L1D': error_invalid,
+ '1NV4L1D': error_invalid,
+ ' b0gUS': error_invalid,
+ }
+ self.assertFieldOutput(UKPostcodeField, valid, invalid)
+ valid = {}
+ invalid = {
+ '1NV 4L1D': [u'Enter a bloody postcode!'],
+ }
+ kwargs = {'error_messages': {'invalid': 'Enter a bloody postcode!'}}
+ self.assertFieldOutput(UKPostcodeField, valid, invalid, field_kwargs=kwargs)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/us.py b/parts/django/tests/regressiontests/forms/localflavor/us.py
new file mode 100644
index 0000000..65dd1bb
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/us.py
@@ -0,0 +1,126 @@
+from django.contrib.localflavor.us.forms import (USZipCodeField,
+ USPhoneNumberField, USStateField, USStateSelect, USSocialSecurityNumberField)
+
+from utils import LocalFlavorTestCase
+
+
+class USLocalFlavorTests(LocalFlavorTestCase):
+ def test_USStateSelect(self):
+ f = USStateSelect()
+ out = u'''<select name="state">
+<option value="AL">Alabama</option>
+<option value="AK">Alaska</option>
+<option value="AS">American Samoa</option>
+<option value="AZ">Arizona</option>
+<option value="AR">Arkansas</option>
+<option value="CA">California</option>
+<option value="CO">Colorado</option>
+<option value="CT">Connecticut</option>
+<option value="DE">Delaware</option>
+<option value="DC">District of Columbia</option>
+<option value="FL">Florida</option>
+<option value="GA">Georgia</option>
+<option value="GU">Guam</option>
+<option value="HI">Hawaii</option>
+<option value="ID">Idaho</option>
+<option value="IL" selected="selected">Illinois</option>
+<option value="IN">Indiana</option>
+<option value="IA">Iowa</option>
+<option value="KS">Kansas</option>
+<option value="KY">Kentucky</option>
+<option value="LA">Louisiana</option>
+<option value="ME">Maine</option>
+<option value="MD">Maryland</option>
+<option value="MA">Massachusetts</option>
+<option value="MI">Michigan</option>
+<option value="MN">Minnesota</option>
+<option value="MS">Mississippi</option>
+<option value="MO">Missouri</option>
+<option value="MT">Montana</option>
+<option value="NE">Nebraska</option>
+<option value="NV">Nevada</option>
+<option value="NH">New Hampshire</option>
+<option value="NJ">New Jersey</option>
+<option value="NM">New Mexico</option>
+<option value="NY">New York</option>
+<option value="NC">North Carolina</option>
+<option value="ND">North Dakota</option>
+<option value="MP">Northern Mariana Islands</option>
+<option value="OH">Ohio</option>
+<option value="OK">Oklahoma</option>
+<option value="OR">Oregon</option>
+<option value="PA">Pennsylvania</option>
+<option value="PR">Puerto Rico</option>
+<option value="RI">Rhode Island</option>
+<option value="SC">South Carolina</option>
+<option value="SD">South Dakota</option>
+<option value="TN">Tennessee</option>
+<option value="TX">Texas</option>
+<option value="UT">Utah</option>
+<option value="VT">Vermont</option>
+<option value="VI">Virgin Islands</option>
+<option value="VA">Virginia</option>
+<option value="WA">Washington</option>
+<option value="WV">West Virginia</option>
+<option value="WI">Wisconsin</option>
+<option value="WY">Wyoming</option>
+</select>'''
+ self.assertEquals(f.render('state', 'IL'), out)
+
+ def test_USZipCodeField(self):
+ error_format = [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+ valid = {
+ '60606': '60606',
+ 60606: '60606',
+ '04000': '04000',
+ '60606-1234': '60606-1234',
+ }
+ invalid = {
+ '4000': error_format,
+ '6060-1234': error_format,
+ '60606-': error_format,
+ }
+ self.assertFieldOutput(USZipCodeField, valid, invalid)
+
+ def test_USPhoneNumberField(self):
+ error_format = [u'Phone numbers must be in XXX-XXX-XXXX format.']
+ valid = {
+ '312-555-1212': '312-555-1212',
+ '3125551212': '312-555-1212',
+ '312 555-1212': '312-555-1212',
+ '(312) 555-1212': '312-555-1212',
+ '312 555 1212': '312-555-1212',
+ '312.555.1212': '312-555-1212',
+ '312.555-1212': '312-555-1212',
+ ' (312) 555.1212 ': '312-555-1212',
+ }
+ invalid = {
+ '555-1212': error_format,
+ '312-55-1212': error_format,
+ }
+ self.assertFieldOutput(USPhoneNumberField, valid, invalid)
+
+ def test_USStateField(self):
+ error_invalid = [u'Enter a U.S. state or territory.']
+ valid = {
+ 'il': 'IL',
+ 'IL': 'IL',
+ 'illinois': 'IL',
+ ' illinois ': 'IL',
+ }
+ invalid = {
+ 60606: error_invalid,
+ }
+ self.assertFieldOutput(USStateField, valid, invalid)
+
+ def test_USSocialSecurityNumberField(self):
+ error_invalid = [u'Enter a valid U.S. Social Security number in XXX-XX-XXXX format.']
+
+ valid = {
+ '987-65-4330': '987-65-4330',
+ '987654330': '987-65-4330',
+ }
+ invalid = {
+ '078-05-1120': error_invalid,
+ }
+ self.assertFieldOutput(USSocialSecurityNumberField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/utils.py b/parts/django/tests/regressiontests/forms/localflavor/utils.py
new file mode 100644
index 0000000..a10258f
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/utils.py
@@ -0,0 +1,51 @@
+from unittest import TestCase
+
+from django.core.exceptions import ValidationError
+from django.core.validators import EMPTY_VALUES
+
+
+class LocalFlavorTestCase(TestCase):
+ def assertFieldOutput(self, fieldclass, valid, invalid, field_args=[],
+ field_kwargs={}, empty_value=u''):
+ """Asserts that a field behaves correctly with various inputs.
+
+ Args:
+ fieldclass: the class of the field to be tested.
+ valid: a dictionary mapping valid inputs to their expected
+ cleaned values.
+ invalid: a dictionary mapping invalid inputs to one or more
+ raised error messages.
+ fieldargs: the args passed to instantiate the field
+ fieldkwargs: the kwargs passed to instantiate the field
+ emptyvalue: the expected clean output for inputs in EMPTY_VALUES
+ """
+ required = fieldclass(*field_args, **field_kwargs)
+ optional = fieldclass(*field_args, **dict(field_kwargs, required=False))
+ # test valid inputs
+ for input, output in valid.items():
+ self.assertEqual(required.clean(input), output)
+ self.assertEqual(optional.clean(input), output)
+ # test invalid inputs
+ for input, errors in invalid.items():
+ try:
+ required.clean(input)
+ except ValidationError, e:
+ self.assertEqual(errors, e.messages)
+ else:
+ self.fail()
+ try:
+ optional.clean(input)
+ except ValidationError, e:
+ self.assertEqual(errors, e.messages)
+ else:
+ self.fail()
+ # test required inputs
+ error_required = [u'This field is required.']
+ for val in EMPTY_VALUES:
+ try:
+ required.clean(val)
+ except ValidationError, e:
+ self.assertEqual(error_required, e.messages)
+ else:
+ self.fail()
+ self.assertEqual(optional.clean(val), empty_value)
diff --git a/parts/django/tests/regressiontests/forms/localflavor/uy.py b/parts/django/tests/regressiontests/forms/localflavor/uy.py
new file mode 100644
index 0000000..2b9e134
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/uy.py
@@ -0,0 +1,52 @@
+from django.contrib.localflavor.uy.forms import UYDepartamentSelect, UYCIField
+from django.contrib.localflavor.uy.util import get_validation_digit
+
+from utils import LocalFlavorTestCase
+
+
+class UYLocalFlavorTests(LocalFlavorTestCase):
+ def test_UYDepartmentSelect(self):
+ f = UYDepartamentSelect()
+ out = u'''<select name="departamentos">
+<option value="G">Artigas</option>
+<option value="A">Canelones</option>
+<option value="E">Cerro Largo</option>
+<option value="L">Colonia</option>
+<option value="Q">Durazno</option>
+<option value="N">Flores</option>
+<option value="O">Florida</option>
+<option value="P">Lavalleja</option>
+<option value="B">Maldonado</option>
+<option value="S" selected="selected">Montevideo</option>
+<option value="I">Paysand\xfa</option>
+<option value="J">R\xedo Negro</option>
+<option value="F">Rivera</option>
+<option value="C">Rocha</option>
+<option value="H">Salto</option>
+<option value="M">San Jos\xe9</option>
+<option value="K">Soriano</option>
+<option value="R">Tacuaremb\xf3</option>
+<option value="D">Treinta y Tres</option>
+</select>'''
+ self.assertEqual(f.render('departamentos', 'S'), out)
+
+ def test_UYCIField(self):
+ error_format = [u'Enter a valid CI number in X.XXX.XXX-X,XXXXXXX-X or XXXXXXXX format.']
+ error_invalid = [u'Enter a valid CI number.']
+ valid = {
+ '4098053': '4098053',
+ '409805-3': '409805-3',
+ '409.805-3': '409.805-3',
+ '10054112': '10054112',
+ '1005411-2': '1005411-2',
+ '1.005.411-2': '1.005.411-2',
+ }
+ invalid = {
+ 'foo': [u'Enter a valid CI number in X.XXX.XXX-X,XXXXXXX-X or XXXXXXXX format.'],
+ '409805-2': [u'Enter a valid CI number.'],
+ '1.005.411-5': [u'Enter a valid CI number.'],
+ }
+ self.assertFieldOutput(UYCIField, valid, invalid)
+ self.assertEqual(get_validation_digit(409805), 3)
+ self.assertEqual(get_validation_digit(1005411), 2)
+
diff --git a/parts/django/tests/regressiontests/forms/localflavor/za.py b/parts/django/tests/regressiontests/forms/localflavor/za.py
new file mode 100644
index 0000000..c912421
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavor/za.py
@@ -0,0 +1,29 @@
+from django.contrib.localflavor.za.forms import ZAIDField, ZAPostCodeField
+
+from utils import LocalFlavorTestCase
+
+
+class ZALocalFlavorTests(LocalFlavorTestCase):
+ def test_ZAIDField(self):
+ error_invalid = [u'Enter a valid South African ID number']
+ valid = {
+ '0002290001003': '0002290001003',
+ '000229 0001 003': '0002290001003',
+ }
+ invalid = {
+ '0102290001001': error_invalid,
+ '811208': error_invalid,
+ '0002290001004': error_invalid,
+ }
+ self.assertFieldOutput(ZAIDField, valid, invalid)
+
+ def test_ZAPostCodeField(self):
+ error_invalid = [u'Enter a valid South African postal code']
+ valid = {
+ '0000': '0000',
+ }
+ invalid = {
+ 'abcd': error_invalid,
+ ' 7530': error_invalid,
+ }
+ self.assertFieldOutput(ZAPostCodeField, valid, invalid)
diff --git a/parts/django/tests/regressiontests/forms/localflavortests.py b/parts/django/tests/regressiontests/forms/localflavortests.py
new file mode 100644
index 0000000..2582656
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/localflavortests.py
@@ -0,0 +1,37 @@
+from localflavor.cz import tests as localflavor_cz_tests
+from localflavor.se import tests as localflavor_se_tests
+
+from localflavor.ar import ARLocalFlavorTests
+from localflavor.at import ATLocalFlavorTests
+from localflavor.au import AULocalFlavorTests
+from localflavor.at import ATLocalFlavorTests
+from localflavor.br import BRLocalFlavorTests
+from localflavor.ca import CALocalFlavorTests
+from localflavor.ch import CHLocalFlavorTests
+from localflavor.cl import CLLocalFlavorTests
+from localflavor.de import DELocalFlavorTests
+from localflavor.es import ESLocalFlavorTests
+from localflavor.fi import FILocalFlavorTests
+from localflavor.fr import FRLocalFlavorTests
+from localflavor.generic import GenericLocalFlavorTests
+from localflavor.id import IDLocalFlavorTests
+from localflavor.ie import IELocalFlavorTests
+from localflavor.is_ import ISLocalFlavorTests
+from localflavor.it import ITLocalFlavorTests
+from localflavor.jp import JPLocalFlavorTests
+from localflavor.kw import KWLocalFlavorTests
+from localflavor.nl import NLLocalFlavorTests
+from localflavor.pl import PLLocalFlavorTests
+from localflavor.pt import PTLocalFlavorTests
+from localflavor.ro import ROLocalFlavorTests
+from localflavor.sk import SKLocalFlavorTests
+from localflavor.uk import UKLocalFlavorTests
+from localflavor.us import USLocalFlavorTests
+from localflavor.uy import UYLocalFlavorTests
+from localflavor.za import ZALocalFlavorTests
+
+
+__test__ = {
+ 'localflavor_cz_tests': localflavor_cz_tests,
+ 'localflavor_se_tests': localflavor_se_tests,
+}
diff --git a/parts/django/tests/regressiontests/forms/models.py b/parts/django/tests/regressiontests/forms/models.py
new file mode 100644
index 0000000..203980c
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/models.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+import datetime
+import tempfile
+
+from django.db import models
+from django.core.files.storage import FileSystemStorage
+
+
+temp_storage_location = tempfile.mkdtemp()
+temp_storage = FileSystemStorage(location=temp_storage_location)
+
+
+class BoundaryModel(models.Model):
+ positive_integer = models.PositiveIntegerField(null=True, blank=True)
+
+
+callable_default_value = 0
+def callable_default():
+ global callable_default_value
+ callable_default_value = callable_default_value + 1
+ return callable_default_value
+
+
+class Defaults(models.Model):
+ name = models.CharField(max_length=255, default='class default value')
+ def_date = models.DateField(default = datetime.date(1980, 1, 1))
+ value = models.IntegerField(default=42)
+ callable_default = models.IntegerField(default=callable_default)
+
+
+class ChoiceModel(models.Model):
+ """For ModelChoiceField and ModelMultipleChoiceField tests."""
+ name = models.CharField(max_length=10)
+
+
+class ChoiceOptionModel(models.Model):
+ """Destination for ChoiceFieldModel's ForeignKey.
+ Can't reuse ChoiceModel because error_message tests require that it have no instances."""
+ name = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return u'ChoiceOption %d' % self.pk
+
+
+class ChoiceFieldModel(models.Model):
+ """Model with ForeignKey to another model, for testing ModelForm
+ generation with ModelChoiceField."""
+ choice = models.ForeignKey(ChoiceOptionModel, blank=False,
+ default=lambda: ChoiceOptionModel.objects.get(name='default'))
+ choice_int = models.ForeignKey(ChoiceOptionModel, blank=False, related_name='choice_int',
+ default=lambda: 1)
+
+ multi_choice = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice',
+ default=lambda: ChoiceOptionModel.objects.filter(name='default'))
+ multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int',
+ default=lambda: [1])
+
+
+class FileModel(models.Model):
+ file = models.FileField(storage=temp_storage, upload_to='tests')
+
+
+class Group(models.Model):
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u'%s' % self.name
+
+
+class Cheese(models.Model):
+ name = models.CharField(max_length=100)
diff --git a/parts/django/tests/regressiontests/forms/tests/__init__.py b/parts/django/tests/regressiontests/forms/tests/__init__.py
new file mode 100644
index 0000000..95df6a1
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/__init__.py
@@ -0,0 +1,43 @@
+from error_messages import *
+from extra import *
+from fields import FieldsTests
+from forms import *
+from formsets import *
+from input_formats import *
+from media import *
+from models import *
+from regressions import *
+from util import *
+from validators import TestFieldWithValidators
+from widgets import *
+
+from regressiontests.forms.localflavortests import (
+ __test__,
+ ARLocalFlavorTests,
+ ATLocalFlavorTests,
+ AULocalFlavorTests,
+ BRLocalFlavorTests,
+ CALocalFlavorTests,
+ CHLocalFlavorTests,
+ CLLocalFlavorTests,
+ DELocalFlavorTests,
+ ESLocalFlavorTests,
+ FILocalFlavorTests,
+ FRLocalFlavorTests,
+ GenericLocalFlavorTests,
+ IDLocalFlavorTests,
+ IELocalFlavorTests,
+ ISLocalFlavorTests,
+ ITLocalFlavorTests,
+ JPLocalFlavorTests,
+ KWLocalFlavorTests,
+ NLLocalFlavorTests,
+ PLLocalFlavorTests,
+ PTLocalFlavorTests,
+ ROLocalFlavorTests,
+ SKLocalFlavorTests,
+ UKLocalFlavorTests,
+ USLocalFlavorTests,
+ UYLocalFlavorTests,
+ ZALocalFlavorTests,
+)
diff --git a/parts/django/tests/regressiontests/forms/tests/error_messages.py b/parts/django/tests/regressiontests/forms/tests/error_messages.py
new file mode 100644
index 0000000..39d88a6
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/error_messages.py
@@ -0,0 +1,253 @@
+# -*- coding: utf-8 -*-
+import unittest
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.forms import *
+from django.test import TestCase
+from django.utils.safestring import mark_safe
+
+class AssertFormErrorsMixin(object):
+ def assertFormErrors(self, expected, the_callable, *args, **kwargs):
+ try:
+ the_callable(*args, **kwargs)
+ self.fail("Testing the 'clean' method on %s failed to raise a ValidationError.")
+ except ValidationError, e:
+ self.assertEqual(e.messages, expected)
+
+
+class FormsErrorMessagesTestCase(unittest.TestCase, AssertFormErrorsMixin):
+ def test_charfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'min_length': 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s',
+ 'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s',
+ }
+ f = CharField(min_length=5, max_length=10, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'LENGTH 4, MIN LENGTH 5'], f.clean, '1234')
+ self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901')
+
+ def test_integerfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'min_value': 'MIN VALUE IS %(limit_value)s',
+ 'max_value': 'MAX VALUE IS %(limit_value)s',
+ }
+ f = IntegerField(min_value=5, max_value=10, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+ self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4')
+ self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11')
+
+ def test_floatfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'min_value': 'MIN VALUE IS %(limit_value)s',
+ 'max_value': 'MAX VALUE IS %(limit_value)s',
+ }
+ f = FloatField(min_value=5, max_value=10, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+ self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4')
+ self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11')
+
+ def test_decimalfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'min_value': 'MIN VALUE IS %(limit_value)s',
+ 'max_value': 'MAX VALUE IS %(limit_value)s',
+ 'max_digits': 'MAX DIGITS IS %s',
+ 'max_decimal_places': 'MAX DP IS %s',
+ 'max_whole_digits': 'MAX DIGITS BEFORE DP IS %s',
+ }
+ f = DecimalField(min_value=5, max_value=10, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+ self.assertFormErrors([u'MIN VALUE IS 5'], f.clean, '4')
+ self.assertFormErrors([u'MAX VALUE IS 10'], f.clean, '11')
+
+ f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e)
+ self.assertFormErrors([u'MAX DIGITS IS 4'], f2.clean, '123.45')
+ self.assertFormErrors([u'MAX DP IS 2'], f2.clean, '1.234')
+ self.assertFormErrors([u'MAX DIGITS BEFORE DP IS 2'], f2.clean, '123.4')
+
+ def test_datefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ }
+ f = DateField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+
+ def test_timefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ }
+ f = TimeField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+
+ def test_datetimefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ }
+ f = DateTimeField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+
+ def test_regexfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'min_length': 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s',
+ 'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s',
+ }
+ f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abcde')
+ self.assertFormErrors([u'LENGTH 4, MIN LENGTH 5'], f.clean, '1234')
+ self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, '12345678901')
+
+ def test_emailfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'min_length': 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s',
+ 'max_length': 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s',
+ }
+ f = EmailField(min_length=8, max_length=10, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abcdefgh')
+ self.assertFormErrors([u'LENGTH 7, MIN LENGTH 8'], f.clean, 'a@b.com')
+ self.assertFormErrors([u'LENGTH 11, MAX LENGTH 10'], f.clean, 'aye@bee.com')
+
+ def test_filefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'missing': 'MISSING',
+ 'empty': 'EMPTY FILE',
+ }
+ f = FileField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc')
+ self.assertFormErrors([u'EMPTY FILE'], f.clean, SimpleUploadedFile('name', None))
+ self.assertFormErrors([u'EMPTY FILE'], f.clean, SimpleUploadedFile('name', ''))
+
+ def test_urlfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID',
+ 'invalid_link': 'INVALID LINK',
+ }
+ f = URLField(verify_exists=True, error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID'], f.clean, 'abc.c')
+ self.assertFormErrors([u'INVALID LINK'], f.clean, 'http://www.broken.djangoproject.com')
+
+ def test_booleanfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ }
+ f = BooleanField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+
+ def test_choicefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid_choice': '%(value)s IS INVALID CHOICE',
+ }
+ f = ChoiceField(choices=[('a', 'aye')], error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'b IS INVALID CHOICE'], f.clean, 'b')
+
+ def test_multiplechoicefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid_choice': '%(value)s IS INVALID CHOICE',
+ 'invalid_list': 'NOT A LIST',
+ }
+ f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'NOT A LIST'], f.clean, 'b')
+ self.assertFormErrors([u'b IS INVALID CHOICE'], f.clean, ['b'])
+
+ def test_splitdatetimefield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid_date': 'INVALID DATE',
+ 'invalid_time': 'INVALID TIME',
+ }
+ f = SplitDateTimeField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID DATE', u'INVALID TIME'], f.clean, ['a', 'b'])
+
+ def test_ipaddressfield(self):
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid': 'INVALID IP ADDRESS',
+ }
+ f = IPAddressField(error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
+
+ def test_subclassing_errorlist(self):
+ class TestForm(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birthday = DateField()
+
+ def clean(self):
+ raise ValidationError("I like to be awkward.")
+
+ class CustomErrorList(util.ErrorList):
+ def __unicode__(self):
+ return self.as_divs()
+
+ def as_divs(self):
+ if not self: return u''
+ return mark_safe(u'<div class="error">%s</div>' % ''.join([u'<p>%s</p>' % e for e in self]))
+
+ # This form should print errors the default way.
+ form1 = TestForm({'first_name': 'John'})
+ self.assertEqual(str(form1['last_name'].errors), '<ul class="errorlist"><li>This field is required.</li></ul>')
+ self.assertEqual(str(form1.errors['__all__']), '<ul class="errorlist"><li>I like to be awkward.</li></ul>')
+
+ # This one should wrap error groups in the customized way.
+ form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList)
+ self.assertEqual(str(form2['last_name'].errors), '<div class="error"><p>This field is required.</p></div>')
+ self.assertEqual(str(form2.errors['__all__']), '<div class="error"><p>I like to be awkward.</p></div>')
+
+
+class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin):
+ def test_modelchoicefield(self):
+ # Create choices for the model choice field tests below.
+ from regressiontests.forms.models import ChoiceModel
+ c1 = ChoiceModel.objects.create(pk=1, name='a')
+ c2 = ChoiceModel.objects.create(pk=2, name='b')
+ c3 = ChoiceModel.objects.create(pk=3, name='c')
+
+ # ModelChoiceField
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid_choice': 'INVALID CHOICE',
+ }
+ f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'INVALID CHOICE'], f.clean, '4')
+
+ # ModelMultipleChoiceField
+ e = {
+ 'required': 'REQUIRED',
+ 'invalid_choice': '%s IS INVALID CHOICE',
+ 'list': 'NOT A LIST OF VALUES',
+ }
+ f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
+ self.assertFormErrors([u'REQUIRED'], f.clean, '')
+ self.assertFormErrors([u'NOT A LIST OF VALUES'], f.clean, '3')
+ self.assertFormErrors([u'4 IS INVALID CHOICE'], f.clean, ['4'])
diff --git a/parts/django/tests/regressiontests/forms/tests/extra.py b/parts/django/tests/regressiontests/forms/tests/extra.py
new file mode 100644
index 0000000..bcdf4dd
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/extra.py
@@ -0,0 +1,610 @@
+# -*- coding: utf-8 -*-
+import datetime
+from decimal import Decimal
+import re
+import time
+import unittest
+from django.conf import settings
+from django.forms import *
+from django.forms.extras import SelectDateWidget
+from django.forms.util import ErrorList
+from django.test import TestCase
+from django.utils import translation
+from django.utils.encoding import force_unicode
+from django.utils.encoding import smart_unicode
+from error_messages import AssertFormErrorsMixin
+
+
+class FormsExtraTestCase(unittest.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.assertEqual(w.render('mydate', ''), """<select name="mydate_month" id="id_mydate_month">
+<option value="0">---</option>
+<option value="1">January</option>
+<option value="2">February</option>
+<option value="3">March</option>
+<option value="4">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day" id="id_mydate_day">
+<option value="0">---</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="mydate_year" id="id_mydate_year">
+<option value="0">---</option>
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>""")
+ self.assertEqual(w.render('mydate', None), w.render('mydate', ''))
+
+ self.assertEqual(w.render('mydate', '2010-04-15'), """<select name="mydate_month" id="id_mydate_month">
+<option value="1">January</option>
+<option value="2">February</option>
+<option value="3">March</option>
+<option value="4" selected="selected">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day" id="id_mydate_day">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15" selected="selected">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="mydate_year" id="id_mydate_year">
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010" selected="selected">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>""")
+
+ # Accepts a datetime or a string:
+ self.assertEqual(w.render('mydate', datetime.date(2010, 4, 15)), w.render('mydate', '2010-04-15'))
+
+ # Invalid dates still render the failed date:
+ self.assertEqual(w.render('mydate', '2010-02-31'), """<select name="mydate_month" id="id_mydate_month">
+<option value="1">January</option>
+<option value="2" selected="selected">February</option>
+<option value="3">March</option>
+<option value="4">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day" id="id_mydate_day">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31" selected="selected">31</option>
+</select>
+<select name="mydate_year" id="id_mydate_year">
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010" selected="selected">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>""")
+
+ # Using a SelectDateWidget in a form:
+ w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), required=False)
+ self.assertEqual(w.render('mydate', ''), """<select name="mydate_month" id="id_mydate_month">
+<option value="0">---</option>
+<option value="1">January</option>
+<option value="2">February</option>
+<option value="3">March</option>
+<option value="4">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day" id="id_mydate_day">
+<option value="0">---</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="mydate_year" id="id_mydate_year">
+<option value="0">---</option>
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>""")
+ self.assertEqual(w.render('mydate', '2010-04-15'), """<select name="mydate_month" id="id_mydate_month">
+<option value="0">---</option>
+<option value="1">January</option>
+<option value="2">February</option>
+<option value="3">March</option>
+<option value="4" selected="selected">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day" id="id_mydate_day">
+<option value="0">---</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15" selected="selected">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="mydate_year" id="id_mydate_year">
+<option value="0">---</option>
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010" selected="selected">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>""")
+
+ class GetDate(Form):
+ mydate = DateField(widget=SelectDateWidget)
+
+ a = GetDate({'mydate_month':'4', 'mydate_day':'1', 'mydate_year':'2008'})
+ self.assertTrue(a.is_valid())
+ self.assertEqual(a.cleaned_data['mydate'], datetime.date(2008, 4, 1))
+
+ # As with any widget that implements get_value_from_datadict,
+ # we must be prepared to accept the input from the "as_hidden"
+ # rendering as well.
+
+ self.assertEqual(a['mydate'].as_hidden(), '<input type="hidden" name="mydate" value="2008-4-1" id="id_mydate" />')
+
+ b = GetDate({'mydate':'2008-4-1'})
+ self.assertTrue(b.is_valid())
+ self.assertEqual(b.cleaned_data['mydate'], datetime.date(2008, 4, 1))
+
+ def test_multiwidget(self):
+ # MultiWidget and MultiValueField #############################################
+ # MultiWidgets are widgets composed of other widgets. They are usually
+ # combined with MultiValueFields - a field that is composed of other fields.
+ # MulitWidgets can themselved be composed of other MultiWidgets.
+ # SplitDateTimeWidget is one example of a MultiWidget.
+
+ class ComplexMultiWidget(MultiWidget):
+ def __init__(self, attrs=None):
+ widgets = (
+ TextInput(),
+ SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
+ SplitDateTimeWidget(),
+ )
+ super(ComplexMultiWidget, self).__init__(widgets, attrs)
+
+ def decompress(self, value):
+ if value:
+ data = value.split(',')
+ return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])]
+ return [None, None, None]
+
+ def format_output(self, rendered_widgets):
+ return u'\n'.join(rendered_widgets)
+
+ w = ComplexMultiWidget()
+ self.assertEqual(w.render('name', 'some text,JP,2007-04-25 06:24:00'), """<input type="text" name="name_0" value="some text" />
+<select multiple="multiple" name="name_1">
+<option value="J" selected="selected">John</option>
+<option value="P" selected="selected">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>
+<input type="text" name="name_2_0" value="2007-04-25" /><input type="text" name="name_2_1" value="06:24:00" />""")
+
+ class ComplexField(MultiValueField):
+ def __init__(self, required=True, widget=None, label=None, initial=None):
+ fields = (
+ CharField(),
+ MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
+ SplitDateTimeField()
+ )
+ super(ComplexField, self).__init__(fields, required, widget, label, initial)
+
+ def compress(self, data_list):
+ if data_list:
+ return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
+ return None
+
+ f = ComplexField(widget=w)
+ self.assertEqual(f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']]), u'some text,JP,2007-04-25 06:24:00')
+ self.assertFormErrors([u'Select a valid choice. X is not one of the available choices.'], f.clean, ['some text',['X'], ['2007-04-25','6:24:00']])
+
+ # If insufficient data is provided, None is substituted
+ self.assertFormErrors([u'This field is required.'], f.clean, ['some text',['JP']])
+
+ class ComplexFieldForm(Form):
+ field1 = ComplexField(widget=w)
+
+ f = ComplexFieldForm()
+ self.assertEqual(f.as_table(), """<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" />
+<select multiple="multiple" name="field1_1" id="id_field1_1">
+<option value="J">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>
+<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr>""")
+
+ f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
+ self.assertEqual(f.as_table(), """<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" />
+<select multiple="multiple" name="field1_1" id="id_field1_1">
+<option value="J" selected="selected">John</option>
+<option value="P" selected="selected">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>
+<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>""")
+
+ self.assertEqual(f.cleaned_data['field1'], u'some text,JP,2007-04-25 06:24:00')
+
+ def test_ipaddress(self):
+ f = IPAddressField()
+ self.assertFormErrors([u'This field is required.'], f.clean, '')
+ self.assertFormErrors([u'This field is required.'], f.clean, None)
+ self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
+
+ f = IPAddressField(required=False)
+ self.assertEqual(f.clean(''), u'')
+ self.assertEqual(f.clean(None), u'')
+ self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
+ self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
+
+ def test_smart_unicode(self):
+ class Test:
+ def __str__(self):
+ return 'ŠĐĆŽćžšđ'
+
+ class TestU:
+ def __str__(self):
+ return 'Foo'
+ def __unicode__(self):
+ return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
+
+ self.assertEqual(smart_unicode(Test()), u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
+ self.assertEqual(smart_unicode(TestU()), u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
+ self.assertEqual(smart_unicode(1), u'1')
+ self.assertEqual(smart_unicode('foo'), u'foo')
+
+ def test_accessing_clean(self):
+ class UserForm(Form):
+ username = CharField(max_length=10)
+ password = CharField(widget=PasswordInput)
+
+ def clean(self):
+ data = self.cleaned_data
+
+ if not self.errors:
+ data['username'] = data['username'].lower()
+
+ return data
+
+ f = UserForm({'username': 'SirRobin', 'password': 'blue'})
+ self.assertTrue(f.is_valid())
+ self.assertEqual(f.cleaned_data['username'], u'sirrobin')
+
+ def test_overriding_errorlist(self):
+ class DivErrorList(ErrorList):
+ def __unicode__(self):
+ return self.as_divs()
+
+ def as_divs(self):
+ if not self: return u''
+ return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % force_unicode(e) for e in self])
+
+ class CommentForm(Form):
+ name = CharField(max_length=50, required=False)
+ email = EmailField()
+ comment = CharField()
+
+ data = dict(email='invalid')
+ f = CommentForm(data, auto_id=False, error_class=DivErrorList)
+ self.assertEqual(f.as_p(), """<p>Name: <input type="text" name="name" maxlength="50" /></p>
+<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
+<p>Email: <input type="text" name="email" value="invalid" /></p>
+<div class="errorlist"><div class="error">This field is required.</div></div>
+<p>Comment: <input type="text" name="comment" /></p>""")
+
+ def test_multipart_encoded_form(self):
+ class FormWithoutFile(Form):
+ username = CharField()
+
+ class FormWithFile(Form):
+ username = CharField()
+ file = FileField()
+
+ class FormWithImage(Form):
+ image = ImageField()
+
+ self.assertFalse(FormWithoutFile().is_multipart())
+ self.assertTrue(FormWithFile().is_multipart())
+ self.assertTrue(FormWithImage().is_multipart())
+
+
+class FormsExtraL10NTestCase(unittest.TestCase):
+ def setUp(self):
+ super(FormsExtraL10NTestCase, self).setUp()
+ self.old_use_l10n = getattr(settings, 'USE_L10N', False)
+ settings.USE_L10N = True
+ translation.activate('nl')
+
+ def tearDown(self):
+ translation.deactivate()
+ settings.USE_L10N = self.old_use_l10n
+ super(FormsExtraL10NTestCase, self).tearDown()
+
+ def test_l10n(self):
+ w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), required=False)
+ self.assertEqual(w.value_from_datadict({'date_year': '2010', 'date_month': '8', 'date_day': '13'}, {}, 'date'), '13-08-2010')
+
+ self.assertEqual(w.render('date', '13-08-2010'), """<select name="date_day" id="id_date_day">
+<option value="0">---</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13" selected="selected">13</option>
+<option value="14">14</option>
+<option value="15">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="date_month" id="id_date_month">
+<option value="0">---</option>
+<option value="1">januari</option>
+<option value="2">februari</option>
+<option value="3">maart</option>
+<option value="4">april</option>
+<option value="5">mei</option>
+<option value="6">juni</option>
+<option value="7">juli</option>
+<option value="8" selected="selected">augustus</option>
+<option value="9">september</option>
+<option value="10">oktober</option>
+<option value="11">november</option>
+<option value="12">december</option>
+</select>
+<select name="date_year" id="id_date_year">
+<option value="0">---</option>
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010" selected="selected">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>""")
+
+ # Years before 1900 work
+ w = SelectDateWidget(years=('1899',))
+ self.assertEqual(w.value_from_datadict({'date_year': '1899', 'date_month': '8', 'date_day': '13'}, {}, 'date'), '13-08-1899')
diff --git a/parts/django/tests/regressiontests/forms/tests/fields.py b/parts/django/tests/regressiontests/forms/tests/fields.py
new file mode 100644
index 0000000..133a183
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/fields.py
@@ -0,0 +1,862 @@
+# -*- coding: utf-8 -*-
+"""
+##########
+# Fields #
+##########
+
+Each Field class does some sort of validation. Each Field has a clean() method,
+which either raises django.forms.ValidationError or returns the "clean"
+data -- usually a Unicode object, but, in some rare cases, a list.
+
+Each Field's __init__() takes at least these parameters:
+ required -- Boolean that specifies whether the field is required.
+ True by default.
+ widget -- A Widget class, or instance of a Widget class, that should be
+ used for this Field when displaying it. Each Field has a default
+ Widget that it'll use if you don't specify this. In most cases,
+ the default widget is TextInput.
+ label -- A verbose name for this field, for use in displaying this field in
+ a form. By default, Django will use a "pretty" version of the form
+ field name, if the Field is part of a Form.
+ initial -- A value to use in this Field's initial display. This value is
+ *not* used as a fallback if data isn't given.
+
+Other than that, the Field subclasses have class-specific options for
+__init__(). For example, CharField has a max_length option.
+"""
+import datetime
+import time
+import re
+import os
+from decimal import Decimal
+
+from unittest import TestCase
+
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.forms import *
+from django.forms.widgets import RadioFieldRenderer
+
+
+def fix_os_paths(x):
+ if isinstance(x, basestring):
+ return x.replace('\\', '/')
+ elif isinstance(x, tuple):
+ return tuple(fix_os_paths(list(x)))
+ elif isinstance(x, list):
+ return [fix_os_paths(y) for y in x]
+ else:
+ return x
+
+
+class FieldsTests(TestCase):
+
+ def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ # CharField ###################################################################
+
+ def test_charfield_1(self):
+ f = CharField()
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'hello', f.clean('hello'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3]))
+
+ def test_charfield_2(self):
+ f = CharField(required=False)
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'hello', f.clean('hello'))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3]))
+
+ def test_charfield_3(self):
+ f = CharField(max_length=10, required=False)
+ self.assertEqual(u'12345', f.clean('12345'))
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '1234567890a')
+
+ def test_charfield_4(self):
+ f = CharField(min_length=10, required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345')
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertEqual(u'1234567890a', f.clean('1234567890a'))
+
+ def test_charfield_5(self):
+ f = CharField(min_length=10, required=True)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345')
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertEqual(u'1234567890a', f.clean('1234567890a'))
+
+ # IntegerField ################################################################
+
+ def test_integerfield_1(self):
+ f = IntegerField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(1, f.clean('1'))
+ self.assertEqual(True, isinstance(f.clean('1'), int))
+ self.assertEqual(23, f.clean('23'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 'a')
+ self.assertEqual(42, f.clean(42))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 3.14)
+ self.assertEqual(1, f.clean('1 '))
+ self.assertEqual(1, f.clean(' 1'))
+ self.assertEqual(1, f.clean(' 1 '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a')
+
+ def test_integerfield_2(self):
+ f = IntegerField(required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual('None', repr(f.clean('')))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual('None', repr(f.clean(None)))
+ self.assertEqual(1, f.clean('1'))
+ self.assertEqual(True, isinstance(f.clean('1'), int))
+ self.assertEqual(23, f.clean('23'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 'a')
+ self.assertEqual(1, f.clean('1 '))
+ self.assertEqual(1, f.clean(' 1'))
+ self.assertEqual(1, f.clean(' 1 '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a')
+
+ def test_integerfield_3(self):
+ f = IntegerField(max_value=10)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(1, f.clean(1))
+ self.assertEqual(10, f.clean(10))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, 11)
+ self.assertEqual(10, f.clean('10'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, '11')
+
+ def test_integerfield_4(self):
+ f = IntegerField(min_value=10)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1)
+ self.assertEqual(10, f.clean(10))
+ self.assertEqual(11, f.clean(11))
+ self.assertEqual(10, f.clean('10'))
+ self.assertEqual(11, f.clean('11'))
+
+ def test_integerfield_5(self):
+ f = IntegerField(min_value=10, max_value=20)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1)
+ self.assertEqual(10, f.clean(10))
+ self.assertEqual(11, f.clean(11))
+ self.assertEqual(10, f.clean('10'))
+ self.assertEqual(11, f.clean('11'))
+ self.assertEqual(20, f.clean(20))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 20.']", f.clean, 21)
+
+ # FloatField ##################################################################
+
+ def test_floatfield_1(self):
+ f = FloatField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(1.0, f.clean('1'))
+ self.assertEqual(True, isinstance(f.clean('1'), float))
+ self.assertEqual(23.0, f.clean('23'))
+ self.assertEqual(3.1400000000000001, f.clean('3.14'))
+ self.assertEqual(3.1400000000000001, f.clean(3.14))
+ self.assertEqual(42.0, f.clean(42))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, 'a')
+ self.assertEqual(1.0, f.clean('1.0 '))
+ self.assertEqual(1.0, f.clean(' 1.0'))
+ self.assertEqual(1.0, f.clean(' 1.0 '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a')
+
+ def test_floatfield_2(self):
+ f = FloatField(required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(1.0, f.clean('1'))
+
+ def test_floatfield_3(self):
+ f = FloatField(max_value=1.5, min_value=0.5)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4')
+ self.assertEqual(1.5, f.clean('1.5'))
+ self.assertEqual(0.5, f.clean('0.5'))
+
+ # DecimalField ################################################################
+
+ def test_decimalfield_1(self):
+ f = DecimalField(max_digits=4, decimal_places=2)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(f.clean('1'), Decimal("1"))
+ self.assertEqual(True, isinstance(f.clean('1'), Decimal))
+ self.assertEqual(f.clean('23'), Decimal("23"))
+ self.assertEqual(f.clean('3.14'), Decimal("3.14"))
+ self.assertEqual(f.clean(3.14), Decimal("3.14"))
+ self.assertEqual(f.clean(Decimal('3.14')), Decimal("3.14"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, 'NaN')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, 'Inf')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '-Inf')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, 'a')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, u'łąść')
+ self.assertEqual(f.clean('1.0 '), Decimal("1.0"))
+ self.assertEqual(f.clean(' 1.0'), Decimal("1.0"))
+ self.assertEqual(f.clean(' 1.0 '), Decimal("1.0"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '123.45')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '1.234')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 digits before the decimal point.']", f.clean, '123.4')
+ self.assertEqual(f.clean('-12.34'), Decimal("-12.34"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-123.45')
+ self.assertEqual(f.clean('-.12'), Decimal("-0.12"))
+ self.assertEqual(f.clean('-00.12'), Decimal("-0.12"))
+ self.assertEqual(f.clean('-000.12'), Decimal("-0.12"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '-000.123')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-000.12345')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '--0.12')
+
+ def test_decimalfield_2(self):
+ f = DecimalField(max_digits=4, decimal_places=2, required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(f.clean('1'), Decimal("1"))
+
+ def test_decimalfield_3(self):
+ f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4')
+ self.assertEqual(f.clean('1.5'), Decimal("1.5"))
+ self.assertEqual(f.clean('0.5'), Decimal("0.5"))
+ self.assertEqual(f.clean('.5'), Decimal("0.5"))
+ self.assertEqual(f.clean('00.50'), Decimal("0.50"))
+
+ def test_decimalfield_4(self):
+ f = DecimalField(decimal_places=2)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '0.00000001')
+
+ def test_decimalfield_5(self):
+ f = DecimalField(max_digits=3)
+ # Leading whole zeros "collapse" to one digit.
+ self.assertEqual(f.clean('0000000.10'), Decimal("0.1"))
+ # But a leading 0 before the . doesn't count towards max_digits
+ self.assertEqual(f.clean('0000000.100'), Decimal("0.100"))
+ # Only leading whole zeros "collapse" to one digit.
+ self.assertEqual(f.clean('000000.02'), Decimal('0.02'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 3 digits in total.']", f.clean, '000000.0002')
+ self.assertEqual(f.clean('.002'), Decimal("0.002"))
+
+ def test_decimalfield_6(self):
+ f = DecimalField(max_digits=2, decimal_places=2)
+ self.assertEqual(f.clean('.01'), Decimal(".01"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 0 digits before the decimal point.']", f.clean, '1.1')
+
+ # DateField ###################################################################
+
+ def test_datefield_1(self):
+ f = DateField()
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006-10-25'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('10/25/2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('10/25/06'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('Oct 25 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25, 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October, 2006'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '2006-4-31')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '200a-10-25')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '25/10/06')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+
+ def test_datefield_2(self):
+ f = DateField(required=False)
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual('None', repr(f.clean(None)))
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual('None', repr(f.clean('')))
+
+ def test_datefield_3(self):
+ f = DateField(input_formats=['%Y %m %d'])
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006 10 25'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '2006-10-25')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/2006')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/06')
+
+ # TimeField ###################################################################
+
+ def test_timefield_1(self):
+ f = TimeField()
+ self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25)))
+ self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59)))
+ self.assertEqual(datetime.time(14, 25), f.clean('14:25'))
+ self.assertEqual(datetime.time(14, 25, 59), f.clean('14:25:59'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '1:24 p.m.')
+
+ def test_timefield_2(self):
+ f = TimeField(input_formats=['%I:%M %p'])
+ self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25)))
+ self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59)))
+ self.assertEqual(datetime.time(4, 25), f.clean('4:25 AM'))
+ self.assertEqual(datetime.time(16, 25), f.clean('4:25 PM'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '14:30:45')
+
+ # DateTimeField ###############################################################
+
+ def test_datetimefield_1(self):
+ f = DateTimeField()
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59, 200), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('2006-10-25 14:30:45'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30:00'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('2006-10-25'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/2006 14:30:45'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30:00'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/2006'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/06 14:30:45'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30:00'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/06'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 4:30 p.m.')
+
+ def test_datetimefield_2(self):
+ f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59, 200), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006 10 25 2:30 PM'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 14:30:45')
+
+ def test_datetimefield_3(self):
+ f = DateTimeField(required=False)
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual('None', repr(f.clean(None)))
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual('None', repr(f.clean('')))
+
+ # RegexField ##################################################################
+
+ def test_regexfield_1(self):
+ f = RegexField('^\d[A-F]\d$')
+ self.assertEqual(u'2A2', f.clean('2A2'))
+ self.assertEqual(u'3F3', f.clean('3F3'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+
+ def test_regexfield_2(self):
+ f = RegexField('^\d[A-F]\d$', required=False)
+ self.assertEqual(u'2A2', f.clean('2A2'))
+ self.assertEqual(u'3F3', f.clean('3F3'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
+ self.assertEqual(u'', f.clean(''))
+
+ def test_regexfield_3(self):
+ f = RegexField(re.compile('^\d[A-F]\d$'))
+ self.assertEqual(u'2A2', f.clean('2A2'))
+ self.assertEqual(u'3F3', f.clean('3F3'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ')
+
+ def test_regexfield_4(self):
+ f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.')
+ self.assertEqual(u'1234', f.clean('1234'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, '123')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, 'abcd')
+
+ def test_regexfield_5(self):
+ f = RegexField('^\d+$', min_length=5, max_length=10)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, '123')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).', u'Enter a valid value.']", f.clean, 'abc')
+ self.assertEqual(u'12345', f.clean('12345'))
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '12345678901')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '12345a')
+
+ # EmailField ##################################################################
+
+ def test_emailfield_1(self):
+ f = EmailField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(u'person@example.com', f.clean('person@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@invalid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@-invalid.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.alid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.-alid.com')
+ self.assertEqual(u'example@valid-----hyphens.com', f.clean('example@valid-----hyphens.com'))
+ self.assertEqual(u'example@valid-with-hyphens.com', f.clean('example@valid-with-hyphens.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@.com')
+ self.assertEqual(u'local@domain.with.idn.xyz\xe4\xf6\xfc\xdfabc.part.com', f.clean('local@domain.with.idn.xyzäöüßabc.part.com'))
+
+ def test_email_regexp_for_performance(self):
+ f = EmailField()
+ # Check for runaway regex security problem. This will take for-freeking-ever
+ # if the security fix isn't in place.
+ self.assertRaisesErrorWithMessage(
+ ValidationError,
+ "[u'Enter a valid e-mail address.']",
+ f.clean,
+ 'viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058'
+ )
+
+ def test_emailfield_2(self):
+ f = EmailField(required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'person@example.com', f.clean('person@example.com'))
+ self.assertEqual(u'example@example.com', f.clean(' example@example.com \t \t '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar')
+
+ def test_emailfield_3(self):
+ f = EmailField(min_length=10, max_length=15)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 9).']", f.clean, 'a@foo.com')
+ self.assertEqual(u'alf@foo.com', f.clean('alf@foo.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 15 characters (it has 20).']", f.clean, 'alf123456788@foo.com')
+
+ # FileField ##################################################################
+
+ def test_filefield_1(self):
+ f = FileField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '', '')
+ self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None, '')
+ self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', ''))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', ''), '')
+ self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, 'some content that is not a file')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', None))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', ''))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'))))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'))))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf')))
+
+ def test_filefield_2(self):
+ f = FileField(max_length = 5)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this filename has at most 5 characters (it has 18).']", f.clean, SimpleUploadedFile('test_maxlength.txt', 'hello world'))
+ self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf'))
+ self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf'))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'))))
+
+ # URLField ##################################################################
+
+ def test_urlfield_1(self):
+ f = URLField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(u'http://localhost/', f.clean('http://localhost'))
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertEqual(u'http://example.com./', f.clean('http://example.com.'))
+ self.assertEqual(u'http://www.example.com/', f.clean('http://www.example.com'))
+ self.assertEqual(u'http://www.example.com:8000/test', f.clean('http://www.example.com:8000/test'))
+ self.assertEqual(u'http://valid-with-hyphens.com/', f.clean('valid-with-hyphens.com'))
+ self.assertEqual(u'http://subdomain.domain.com/', f.clean('subdomain.domain.com'))
+ self.assertEqual(u'http://200.8.9.10/', f.clean('http://200.8.9.10'))
+ self.assertEqual(u'http://200.8.9.10:8000/test', f.clean('http://200.8.9.10:8000/test'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'com.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, '.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://invalid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://-invalid.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.alid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.-alid.com')
+ self.assertEqual(u'http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com'))
+ self.assertEqual(u'http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah', f.clean('http://some.idn.xyzäöüßabc.domain.com:123/blah'))
+ self.assertEqual(u'http://www.example.com/s/http://code.djangoproject.com/ticket/13804', f.clean('www.example.com/s/http://code.djangoproject.com/ticket/13804'))
+
+ def test_url_regex_ticket11198(self):
+ f = URLField()
+ # hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*200,))
+
+ # a second test, to make sure the problem is really addressed, even on
+ # domains that don't fail the domain label length check in the regex
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*60,))
+
+ def test_urlfield_2(self):
+ f = URLField(required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertEqual(u'http://www.example.com/', f.clean('http://www.example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com')
+
+ def test_urlfield_3(self):
+ f = URLField(verify_exists=True)
+ self.assertEqual(u'http://www.google.com/', f.clean('http://www.google.com')) # This will fail if there's no Internet connection
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
+ self.assertRaises(ValidationError, f.clean, 'http://www.broken.djangoproject.com') # bad domain
+ try:
+ f.clean('http://www.broken.djangoproject.com') # bad domain
+ except ValidationError, e:
+ self.assertEqual("[u'This URL appears to be a broken link.']", str(e))
+ self.assertRaises(ValidationError, f.clean, 'http://google.com/we-love-microsoft.html') # good domain, bad page
+ try:
+ f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page
+ except ValidationError, e:
+ self.assertEqual("[u'This URL appears to be a broken link.']", str(e))
+ # Valid and existent IDN
+ self.assertEqual(u'http://\u05e2\u05d1\u05e8\u05d9\u05ea.idn.icann.org/', f.clean(u'http://עברית.idn.icann.org/'))
+ # Valid but non-existent IDN
+ try:
+ f.clean(u'http://broken.עברית.idn.icann.org/')
+ except ValidationError, e:
+ self.assertEqual("[u'This URL appears to be a broken link.']", str(e))
+
+ def test_urlfield_4(self):
+ f = URLField(verify_exists=True, required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'http://www.google.com/', f.clean('http://www.google.com')) # This will fail if there's no Internet connection
+
+ def test_urlfield_5(self):
+ f = URLField(min_length=15, max_length=20)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 15 characters (it has 13).']", f.clean, 'http://f.com')
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 38).']", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com')
+
+ def test_urlfield_6(self):
+ f = URLField(required=False)
+ self.assertEqual(u'http://example.com/', f.clean('example.com'))
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'https://example.com/', f.clean('https://example.com'))
+
+ def test_urlfield_7(self):
+ f = URLField()
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertEqual(u'http://example.com/test', f.clean('http://example.com/test'))
+
+ def test_urlfield_ticket11826(self):
+ f = URLField()
+ self.assertEqual(u'http://example.com/?some_param=some_value', f.clean('http://example.com?some_param=some_value'))
+
+ # BooleanField ################################################################
+
+ def test_booleanfield_1(self):
+ f = BooleanField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(True, f.clean(True))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, False)
+ self.assertEqual(True, f.clean(1))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, 0)
+ self.assertEqual(True, f.clean('Django rocks'))
+ self.assertEqual(True, f.clean('True'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, 'False')
+
+ def test_booleanfield_2(self):
+ f = BooleanField(required=False)
+ self.assertEqual(False, f.clean(''))
+ self.assertEqual(False, f.clean(None))
+ self.assertEqual(True, f.clean(True))
+ self.assertEqual(False, f.clean(False))
+ self.assertEqual(True, f.clean(1))
+ self.assertEqual(False, f.clean(0))
+ self.assertEqual(True, f.clean('1'))
+ self.assertEqual(False, f.clean('0'))
+ self.assertEqual(True, f.clean('Django rocks'))
+ self.assertEqual(False, f.clean('False'))
+
+ # ChoiceField #################################################################
+
+ def test_choicefield_1(self):
+ f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'1', f.clean('1'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3')
+
+ def test_choicefield_2(self):
+ f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'1', f.clean('1'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3')
+
+ def test_choicefield_3(self):
+ f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
+ self.assertEqual(u'J', f.clean('J'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. John is not one of the available choices.']", f.clean, 'John')
+
+ def test_choicefield_4(self):
+ f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'1', f.clean('1'))
+ self.assertEqual(u'3', f.clean(3))
+ self.assertEqual(u'3', f.clean('3'))
+ self.assertEqual(u'5', f.clean(5))
+ self.assertEqual(u'5', f.clean('5'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, '6')
+
+ # TypedChoiceField ############################################################
+ # TypedChoiceField is just like ChoiceField, except that coerced types will
+ # be returned:
+
+ def test_typedchoicefield_1(self):
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
+ self.assertEqual(1, f.clean('1'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, '2')
+
+ def test_typedchoicefield_2(self):
+ # Different coercion, same validation.
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float)
+ self.assertEqual(1.0, f.clean('1'))
+
+ def test_typedchoicefield_3(self):
+ # This can also cause weirdness: be careful (bool(-1) == True, remember)
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool)
+ self.assertEqual(True, f.clean('-1'))
+
+ def test_typedchoicefield_4(self):
+ # Even more weirdness: if you have a valid choice but your coercion function
+ # can't coerce, you'll still get a validation error. Don't do this!
+ f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. B is not one of the available choices.']", f.clean, 'B')
+ # Required fields require values
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+
+ def test_typedchoicefield_5(self):
+ # Non-required fields aren't required
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
+ self.assertEqual('', f.clean(''))
+ # If you want cleaning an empty value to return a different type, tell the field
+
+ def test_typedchoicefield_6(self):
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
+ self.assertEqual(None, f.clean(''))
+
+ # NullBooleanField ############################################################
+
+ def test_nullbooleanfield_1(self):
+ f = NullBooleanField()
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(True, f.clean(True))
+ self.assertEqual(False, f.clean(False))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(False, f.clean('0'))
+ self.assertEqual(True, f.clean('1'))
+ self.assertEqual(None, f.clean('2'))
+ self.assertEqual(None, f.clean('3'))
+ self.assertEqual(None, f.clean('hello'))
+
+
+ def test_nullbooleanfield_2(self):
+ # Make sure that the internal value is preserved if using HiddenInput (#7753)
+ class HiddenNullBooleanForm(Form):
+ hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
+ hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
+ f = HiddenNullBooleanForm()
+ self.assertEqual('<input type="hidden" name="hidden_nullbool1" value="True" id="id_hidden_nullbool1" /><input type="hidden" name="hidden_nullbool2" value="False" id="id_hidden_nullbool2" />', str(f))
+
+ def test_nullbooleanfield_3(self):
+ class HiddenNullBooleanForm(Form):
+ hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
+ hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
+ f = HiddenNullBooleanForm({ 'hidden_nullbool1': 'True', 'hidden_nullbool2': 'False' })
+ self.assertEqual(None, f.full_clean())
+ self.assertEqual(True, f.cleaned_data['hidden_nullbool1'])
+ self.assertEqual(False, f.cleaned_data['hidden_nullbool2'])
+
+ def test_nullbooleanfield_4(self):
+ # Make sure we're compatible with MySQL, which uses 0 and 1 for its boolean
+ # values. (#9609)
+ NULLBOOL_CHOICES = (('1', 'Yes'), ('0', 'No'), ('', 'Unknown'))
+ class MySQLNullBooleanForm(Form):
+ nullbool0 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
+ nullbool1 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
+ nullbool2 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
+ f = MySQLNullBooleanForm({ 'nullbool0': '1', 'nullbool1': '0', 'nullbool2': '' })
+ self.assertEqual(None, f.full_clean())
+ self.assertEqual(True, f.cleaned_data['nullbool0'])
+ self.assertEqual(False, f.cleaned_data['nullbool1'])
+ self.assertEqual(None, f.cleaned_data['nullbool2'])
+
+ # MultipleChoiceField #########################################################
+
+ def test_multiplechoicefield_1(self):
+ f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual([u'1'], f.clean([1]))
+ self.assertEqual([u'1'], f.clean(['1']))
+ self.assertEqual([u'1', u'2'], f.clean(['1', '2']))
+ self.assertEqual([u'1', u'2'], f.clean([1, '2']))
+ self.assertEqual([u'1', u'2'], f.clean((1, '2')))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, [])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, ())
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3'])
+
+ def test_multiplechoicefield_2(self):
+ f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
+ self.assertEqual([], f.clean(''))
+ self.assertEqual([], f.clean(None))
+ self.assertEqual([u'1'], f.clean([1]))
+ self.assertEqual([u'1'], f.clean(['1']))
+ self.assertEqual([u'1', u'2'], f.clean(['1', '2']))
+ self.assertEqual([u'1', u'2'], f.clean([1, '2']))
+ self.assertEqual([u'1', u'2'], f.clean((1, '2')))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertEqual([], f.clean([]))
+ self.assertEqual([], f.clean(()))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3'])
+
+ def test_multiplechoicefield_3(self):
+ f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
+ self.assertEqual([u'1'], f.clean([1]))
+ self.assertEqual([u'1'], f.clean(['1']))
+ self.assertEqual([u'1', u'5'], f.clean([1, 5]))
+ self.assertEqual([u'1', u'5'], f.clean([1, '5']))
+ self.assertEqual([u'1', u'5'], f.clean(['1', 5]))
+ self.assertEqual([u'1', u'5'], f.clean(['1', '5']))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['6'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['1','6'])
+
+ # ComboField ##################################################################
+
+ def test_combofield_1(self):
+ f = ComboField(fields=[CharField(max_length=20), EmailField()])
+ self.assertEqual(u'test@example.com', f.clean('test@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+
+ def test_combofield_2(self):
+ f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False)
+ self.assertEqual(u'test@example.com', f.clean('test@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail')
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+
+ # FilePathField ###############################################################
+
+ def test_filepathfield_1(self):
+ path = os.path.abspath(forms.__file__)
+ path = os.path.dirname(path) + '/'
+ self.assertTrue(fix_os_paths(path).endswith('/django/forms/'))
+
+ def test_filepathfield_2(self):
+ path = forms.__file__
+ path = os.path.dirname(os.path.abspath(path)) + '/'
+ f = FilePathField(path=path)
+ f.choices = [p for p in f.choices if p[0].endswith('.py')]
+ f.choices.sort()
+ expected = [
+ ('/django/forms/__init__.py', '__init__.py'),
+ ('/django/forms/fields.py', 'fields.py'),
+ ('/django/forms/forms.py', 'forms.py'),
+ ('/django/forms/formsets.py', 'formsets.py'),
+ ('/django/forms/models.py', 'models.py'),
+ ('/django/forms/util.py', 'util.py'),
+ ('/django/forms/widgets.py', 'widgets.py')
+ ]
+ for exp, got in zip(expected, fix_os_paths(f.choices)):
+ self.assertEqual(exp[1], got[1])
+ self.assertTrue(got[0].endswith(exp[0]))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. fields.py is not one of the available choices.']", f.clean, 'fields.py')
+ assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py')
+
+ def test_filepathfield_3(self):
+ path = forms.__file__
+ path = os.path.dirname(os.path.abspath(path)) + '/'
+ f = FilePathField(path=path, match='^.*?\.py$')
+ f.choices.sort()
+ expected = [
+ ('/django/forms/__init__.py', '__init__.py'),
+ ('/django/forms/fields.py', 'fields.py'),
+ ('/django/forms/forms.py', 'forms.py'),
+ ('/django/forms/formsets.py', 'formsets.py'),
+ ('/django/forms/models.py', 'models.py'),
+ ('/django/forms/util.py', 'util.py'),
+ ('/django/forms/widgets.py', 'widgets.py')
+ ]
+ for exp, got in zip(expected, fix_os_paths(f.choices)):
+ self.assertEqual(exp[1], got[1])
+ self.assertTrue(got[0].endswith(exp[0]))
+
+ def test_filepathfield_4(self):
+ path = os.path.abspath(forms.__file__)
+ path = os.path.dirname(path) + '/'
+ f = FilePathField(path=path, recursive=True, match='^.*?\.py$')
+ f.choices.sort()
+ expected = [
+ ('/django/forms/__init__.py', '__init__.py'),
+ ('/django/forms/extras/__init__.py', 'extras/__init__.py'),
+ ('/django/forms/extras/widgets.py', 'extras/widgets.py'),
+ ('/django/forms/fields.py', 'fields.py'),
+ ('/django/forms/forms.py', 'forms.py'),
+ ('/django/forms/formsets.py', 'formsets.py'),
+ ('/django/forms/models.py', 'models.py'),
+ ('/django/forms/util.py', 'util.py'),
+ ('/django/forms/widgets.py', 'widgets.py')
+ ]
+ for exp, got in zip(expected, fix_os_paths(f.choices)):
+ self.assertEqual(exp[1], got[1])
+ self.assertTrue(got[0].endswith(exp[0]))
+
+ # SplitDateTimeField ##########################################################
+
+ def test_splitdatetimefield_1(self):
+ from django.forms.widgets import SplitDateTimeWidget
+ f = SplitDateTimeField()
+ assert isinstance(f.widget, SplitDateTimeWidget)
+ self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.', u'Enter a valid time.']", f.clean, ['hello', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30'])
+
+ def test_splitdatetimefield_2(self):
+ f = SplitDateTimeField(required=False)
+ self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]))
+ self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean(['2006-01-10', '07:30']))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(['']))
+ self.assertEqual(None, f.clean(['', '']))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.', u'Enter a valid time.']", f.clean, ['hello', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', ''])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['', '07:30'])
diff --git a/parts/django/tests/regressiontests/forms/tests/forms.py b/parts/django/tests/regressiontests/forms/tests/forms.py
new file mode 100644
index 0000000..e555a77
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/forms.py
@@ -0,0 +1,1700 @@
+# -*- coding: utf-8 -*-
+import datetime
+from decimal import Decimal
+import re
+import time
+from unittest import TestCase
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.forms import *
+from django.http import QueryDict
+from django.template import Template, Context
+from django.utils.datastructures import MultiValueDict, MergeDict
+from django.utils.safestring import mark_safe
+
+
+class Person(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birthday = DateField()
+
+
+class PersonNew(Form):
+ first_name = CharField(widget=TextInput(attrs={'id': 'first_name_id'}))
+ last_name = CharField()
+ birthday = DateField()
+
+
+class FormsTestCase(TestCase):
+ # A Form is a collection of Fields. It knows how to validate a set of data and it
+ # knows how to render itself in a couple of default ways (e.g., an HTML table).
+ # You can pass it data in __init__(), as a dictionary.
+
+ def test_form(self):
+ # Pass a dictionary to a Form's __init__().
+ p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'})
+
+ self.assertTrue(p.is_bound)
+ self.assertEqual(p.errors, {})
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.errors.as_ul(), u'')
+ self.assertEqual(p.errors.as_text(), u'')
+ self.assertEqual(p.cleaned_data["first_name"], u'John')
+ self.assertEqual(p.cleaned_data["last_name"], u'Lennon')
+ self.assertEqual(p.cleaned_data["birthday"], datetime.date(1940, 10, 9))
+ self.assertEqual(str(p['first_name']), '<input type="text" name="first_name" value="John" id="id_first_name" />')
+ self.assertEqual(str(p['last_name']), '<input type="text" name="last_name" value="Lennon" id="id_last_name" />')
+ self.assertEqual(str(p['birthday']), '<input type="text" name="birthday" value="1940-10-9" id="id_birthday" />')
+ try:
+ p['nonexistentfield']
+ self.fail('Attempts to access non-existent fields should fail.')
+ except KeyError:
+ pass
+
+ form_output = []
+
+ for boundfield in p:
+ form_output.append(str(boundfield))
+
+ self.assertEqual('\n'.join(form_output), """<input type="text" name="first_name" value="John" id="id_first_name" />
+<input type="text" name="last_name" value="Lennon" id="id_last_name" />
+<input type="text" name="birthday" value="1940-10-9" id="id_birthday" />""")
+
+ form_output = []
+
+ for boundfield in p:
+ form_output.append([boundfield.label, boundfield.data])
+
+ self.assertEqual(form_output, [
+ ['First name', u'John'],
+ ['Last name', u'Lennon'],
+ ['Birthday', u'1940-10-9']
+ ])
+ self.assertEqual(str(p), """<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>""")
+
+ def test_empty_dict(self):
+ # Empty dictionaries are valid, too.
+ p = Person({})
+ self.assertTrue(p.is_bound)
+ self.assertEqual(p.errors['first_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['last_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['birthday'], [u'This field is required.'])
+ self.assertFalse(p.is_valid())
+ try:
+ p.cleaned_data
+ self.fail('Attempts to access cleaned_data when validation fails should fail.')
+ except AttributeError:
+ pass
+ self.assertEqual(str(p), """<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>""")
+ self.assertEqual(p.as_table(), """<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>""")
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>""")
+ self.assertEqual(p.as_p(), """<ul class="errorlist"><li>This field is required.</li></ul>
+<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
+<ul class="errorlist"><li>This field is required.</li></ul>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
+<ul class="errorlist"><li>This field is required.</li></ul>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>""")
+
+ def test_unbound_form(self):
+ # If you don't pass any values to the Form's __init__(), or if you pass None,
+ # the Form will be considered unbound and won't do any validation. Form.errors
+ # will be an empty dictionary *but* Form.is_valid() will return False.
+ p = Person()
+ self.assertFalse(p.is_bound)
+ self.assertEqual(p.errors, {})
+ self.assertFalse(p.is_valid())
+ try:
+ p.cleaned_data
+ self.fail('Attempts to access cleaned_data when validation fails should fail.')
+ except AttributeError:
+ pass
+ self.assertEqual(str(p), """<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>""")
+ self.assertEqual(p.as_table(), """<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>""")
+ self.assertEqual(p.as_ul(), """<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>""")
+ self.assertEqual(p.as_p(), """<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>""")
+
+ def test_unicode_values(self):
+ # Unicode values are handled properly.
+ p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'})
+ self.assertEqual(p.as_table(), u'<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>\n<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></td></tr>\n<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>')
+ self.assertEqual(p.as_ul(), u'<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></li>\n<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></li>\n<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></li>')
+ self.assertEqual(p.as_p(), u'<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p>\n<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></p>\n<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>')
+
+ p = Person({'last_name': u'Lennon'})
+ self.assertEqual(p.errors['first_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['birthday'], [u'This field is required.'])
+ self.assertFalse(p.is_valid())
+ self.assertEqual(p.errors.as_ul(), u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is required.</li></ul></li><li>birthday<ul class="errorlist"><li>This field is required.</li></ul></li></ul>')
+ self.assertEqual(p.errors.as_text(), """* first_name
+ * This field is required.
+* birthday
+ * This field is required.""")
+ try:
+ p.cleaned_data
+ self.fail('Attempts to access cleaned_data when validation fails should fail.')
+ except AttributeError:
+ pass
+ self.assertEqual(p['first_name'].errors, [u'This field is required.'])
+ self.assertEqual(p['first_name'].errors.as_ul(), u'<ul class="errorlist"><li>This field is required.</li></ul>')
+ self.assertEqual(p['first_name'].errors.as_text(), u'* This field is required.')
+
+ p = Person()
+ self.assertEqual(str(p['first_name']), '<input type="text" name="first_name" id="id_first_name" />')
+ self.assertEqual(str(p['last_name']), '<input type="text" name="last_name" id="id_last_name" />')
+ self.assertEqual(str(p['birthday']), '<input type="text" name="birthday" id="id_birthday" />')
+
+ def test_cleaned_data_only_fields(self):
+ # cleaned_data will always *only* contain a key for fields defined in the
+ # Form, even if you pass extra data when you define the Form. In this
+ # example, we pass a bunch of extra fields to the form constructor,
+ # but cleaned_data contains only the form's fields.
+ data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'}
+ p = Person(data)
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.cleaned_data['first_name'], u'John')
+ self.assertEqual(p.cleaned_data['last_name'], u'Lennon')
+ self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9))
+
+ def test_optional_data(self):
+ # cleaned_data will include a key and value for *all* fields defined in the Form,
+ # even if the Form's data didn't include a value for fields that are not
+ # required. In this example, the data dictionary doesn't include a value for the
+ # "nick_name" field, but cleaned_data includes it. For CharFields, it's set to the
+ # empty string.
+ class OptionalPersonForm(Form):
+ first_name = CharField()
+ last_name = CharField()
+ nick_name = CharField(required=False)
+
+ data = {'first_name': u'John', 'last_name': u'Lennon'}
+ f = OptionalPersonForm(data)
+ self.assertTrue(f.is_valid())
+ self.assertEqual(f.cleaned_data['nick_name'], u'')
+ self.assertEqual(f.cleaned_data['first_name'], u'John')
+ self.assertEqual(f.cleaned_data['last_name'], u'Lennon')
+
+ # For DateFields, it's set to None.
+ class OptionalPersonForm(Form):
+ first_name = CharField()
+ last_name = CharField()
+ birth_date = DateField(required=False)
+
+ data = {'first_name': u'John', 'last_name': u'Lennon'}
+ f = OptionalPersonForm(data)
+ self.assertTrue(f.is_valid())
+ self.assertEqual(f.cleaned_data['birth_date'], None)
+ self.assertEqual(f.cleaned_data['first_name'], u'John')
+ self.assertEqual(f.cleaned_data['last_name'], u'Lennon')
+
+ def test_auto_id(self):
+ # "auto_id" tells the Form to add an "id" attribute to each form element.
+ # If it's a string that contains '%s', Django will use that as a format string
+ # into which the field's name will be inserted. It will also put a <label> around
+ # the human-readable labels for a field.
+ p = Person(auto_id='%s_id')
+ self.assertEqual(p.as_table(), """<tr><th><label for="first_name_id">First name:</label></th><td><input type="text" name="first_name" id="first_name_id" /></td></tr>
+<tr><th><label for="last_name_id">Last name:</label></th><td><input type="text" name="last_name" id="last_name_id" /></td></tr>
+<tr><th><label for="birthday_id">Birthday:</label></th><td><input type="text" name="birthday" id="birthday_id" /></td></tr>""")
+ self.assertEqual(p.as_ul(), """<li><label for="first_name_id">First name:</label> <input type="text" name="first_name" id="first_name_id" /></li>
+<li><label for="last_name_id">Last name:</label> <input type="text" name="last_name" id="last_name_id" /></li>
+<li><label for="birthday_id">Birthday:</label> <input type="text" name="birthday" id="birthday_id" /></li>""")
+ self.assertEqual(p.as_p(), """<p><label for="first_name_id">First name:</label> <input type="text" name="first_name" id="first_name_id" /></p>
+<p><label for="last_name_id">Last name:</label> <input type="text" name="last_name" id="last_name_id" /></p>
+<p><label for="birthday_id">Birthday:</label> <input type="text" name="birthday" id="birthday_id" /></p>""")
+
+ def test_auto_id_true(self):
+ # If auto_id is any True value whose str() does not contain '%s', the "id"
+ # attribute will be the name of the field.
+ p = Person(auto_id=True)
+ self.assertEqual(p.as_ul(), """<li><label for="first_name">First name:</label> <input type="text" name="first_name" id="first_name" /></li>
+<li><label for="last_name">Last name:</label> <input type="text" name="last_name" id="last_name" /></li>
+<li><label for="birthday">Birthday:</label> <input type="text" name="birthday" id="birthday" /></li>""")
+
+ def test_auto_id_false(self):
+ # If auto_id is any False value, an "id" attribute won't be output unless it
+ # was manually entered.
+ p = Person(auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>First name: <input type="text" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>""")
+
+ def test_id_on_field(self):
+ # In this example, auto_id is False, but the "id" attribute for the "first_name"
+ # field is given. Also note that field gets a <label>, while the others don't.
+ p = PersonNew(auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><label for="first_name_id">First name:</label> <input type="text" id="first_name_id" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>""")
+
+ def test_auto_id_on_form_and_field(self):
+ # If the "id" attribute is specified in the Form and auto_id is True, the "id"
+ # attribute in the Form gets precedence.
+ p = PersonNew(auto_id=True)
+ self.assertEqual(p.as_ul(), """<li><label for="first_name_id">First name:</label> <input type="text" id="first_name_id" name="first_name" /></li>
+<li><label for="last_name">Last name:</label> <input type="text" name="last_name" id="last_name" /></li>
+<li><label for="birthday">Birthday:</label> <input type="text" name="birthday" id="birthday" /></li>""")
+
+ def test_various_boolean_values(self):
+ class SignupForm(Form):
+ email = EmailField()
+ get_spam = BooleanField()
+
+ f = SignupForm(auto_id=False)
+ self.assertEqual(str(f['email']), '<input type="text" name="email" />')
+ self.assertEqual(str(f['get_spam']), '<input type="checkbox" name="get_spam" />')
+
+ f = SignupForm({'email': 'test@example.com', 'get_spam': True}, auto_id=False)
+ self.assertEqual(str(f['email']), '<input type="text" name="email" value="test@example.com" />')
+ self.assertEqual(str(f['get_spam']), '<input checked="checked" type="checkbox" name="get_spam" />')
+
+ # 'True' or 'true' should be rendered without a value attribute
+ f = SignupForm({'email': 'test@example.com', 'get_spam': 'True'}, auto_id=False)
+ self.assertEqual(str(f['get_spam']), '<input checked="checked" type="checkbox" name="get_spam" />')
+
+ f = SignupForm({'email': 'test@example.com', 'get_spam': 'true'}, auto_id=False)
+ self.assertEqual(str(f['get_spam']), '<input checked="checked" type="checkbox" name="get_spam" />')
+
+ # A value of 'False' or 'false' should be rendered unchecked
+ f = SignupForm({'email': 'test@example.com', 'get_spam': 'False'}, auto_id=False)
+ self.assertEqual(str(f['get_spam']), '<input type="checkbox" name="get_spam" />')
+
+ f = SignupForm({'email': 'test@example.com', 'get_spam': 'false'}, auto_id=False)
+ self.assertEqual(str(f['get_spam']), '<input type="checkbox" name="get_spam" />')
+
+ def test_widget_output(self):
+ # Any Field can have a Widget class passed to its constructor:
+ class ContactForm(Form):
+ subject = CharField()
+ message = CharField(widget=Textarea)
+
+ f = ContactForm(auto_id=False)
+ self.assertEqual(str(f['subject']), '<input type="text" name="subject" />')
+ self.assertEqual(str(f['message']), '<textarea rows="10" cols="40" name="message"></textarea>')
+
+ # as_textarea(), as_text() and as_hidden() are shortcuts for changing the output
+ # widget type:
+ self.assertEqual(f['subject'].as_textarea(), u'<textarea rows="10" cols="40" name="subject"></textarea>')
+ self.assertEqual(f['message'].as_text(), u'<input type="text" name="message" />')
+ self.assertEqual(f['message'].as_hidden(), u'<input type="hidden" name="message" />')
+
+ # The 'widget' parameter to a Field can also be an instance:
+ class ContactForm(Form):
+ subject = CharField()
+ message = CharField(widget=Textarea(attrs={'rows': 80, 'cols': 20}))
+
+ f = ContactForm(auto_id=False)
+ self.assertEqual(str(f['message']), '<textarea rows="80" cols="20" name="message"></textarea>')
+
+ # Instance-level attrs are *not* carried over to as_textarea(), as_text() and
+ # as_hidden():
+ self.assertEqual(f['message'].as_text(), u'<input type="text" name="message" />')
+ f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}, auto_id=False)
+ self.assertEqual(f['subject'].as_textarea(), u'<textarea rows="10" cols="40" name="subject">Hello</textarea>')
+ self.assertEqual(f['message'].as_text(), u'<input type="text" name="message" value="I love you." />')
+ self.assertEqual(f['message'].as_hidden(), u'<input type="hidden" name="message" value="I love you." />')
+
+ def test_forms_with_choices(self):
+ # For a form with a <select>, use ChoiceField:
+ class FrameworkForm(Form):
+ name = CharField()
+ language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')])
+
+ f = FrameworkForm(auto_id=False)
+ self.assertEqual(str(f['language']), """<select name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>""")
+ f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
+ self.assertEqual(str(f['language']), """<select name="language">
+<option value="P" selected="selected">Python</option>
+<option value="J">Java</option>
+</select>""")
+
+ # A subtlety: If one of the choices' value is the empty string and the form is
+ # unbound, then the <option> for the empty-string choice will get selected="selected".
+ class FrameworkForm(Form):
+ name = CharField()
+ language = ChoiceField(choices=[('', '------'), ('P', 'Python'), ('J', 'Java')])
+
+ f = FrameworkForm(auto_id=False)
+ self.assertEqual(str(f['language']), """<select name="language">
+<option value="" selected="selected">------</option>
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>""")
+
+ # You can specify widget attributes in the Widget constructor.
+ class FrameworkForm(Form):
+ name = CharField()
+ language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=Select(attrs={'class': 'foo'}))
+
+ f = FrameworkForm(auto_id=False)
+ self.assertEqual(str(f['language']), """<select class="foo" name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>""")
+ f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
+ self.assertEqual(str(f['language']), """<select class="foo" name="language">
+<option value="P" selected="selected">Python</option>
+<option value="J">Java</option>
+</select>""")
+
+ # When passing a custom widget instance to ChoiceField, note that setting
+ # 'choices' on the widget is meaningless. The widget will use the choices
+ # defined on the Field, not the ones defined on the Widget.
+ class FrameworkForm(Form):
+ name = CharField()
+ language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=Select(choices=[('R', 'Ruby'), ('P', 'Perl')], attrs={'class': 'foo'}))
+
+ f = FrameworkForm(auto_id=False)
+ self.assertEqual(str(f['language']), """<select class="foo" name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>""")
+ f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
+ self.assertEqual(str(f['language']), """<select class="foo" name="language">
+<option value="P" selected="selected">Python</option>
+<option value="J">Java</option>
+</select>""")
+
+ # You can set a ChoiceField's choices after the fact.
+ class FrameworkForm(Form):
+ name = CharField()
+ language = ChoiceField()
+
+ f = FrameworkForm(auto_id=False)
+ self.assertEqual(str(f['language']), """<select name="language">
+</select>""")
+ f.fields['language'].choices = [('P', 'Python'), ('J', 'Java')]
+ self.assertEqual(str(f['language']), """<select name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>""")
+
+ def test_forms_with_radio(self):
+ # Add widget=RadioSelect to use that widget with a ChoiceField.
+ class FrameworkForm(Form):
+ name = CharField()
+ language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=RadioSelect)
+
+ f = FrameworkForm(auto_id=False)
+ self.assertEqual(str(f['language']), """<ul>
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
+</ul>""")
+ self.assertEqual(f.as_table(), """<tr><th>Name:</th><td><input type="text" name="name" /></td></tr>
+<tr><th>Language:</th><td><ul>
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
+</ul></td></tr>""")
+ self.assertEqual(f.as_ul(), """<li>Name: <input type="text" name="name" /></li>
+<li>Language: <ul>
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
+</ul></li>""")
+
+ # Regarding auto_id and <label>, RadioSelect is a special case. Each radio button
+ # gets a distinct ID, formed by appending an underscore plus the button's
+ # zero-based index.
+ f = FrameworkForm(auto_id='id_%s')
+ self.assertEqual(str(f['language']), """<ul>
+<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul>""")
+
+ # When RadioSelect is used with auto_id, and the whole form is printed using
+ # either as_table() or as_ul(), the label for the RadioSelect will point to the
+ # ID of the *first* radio button.
+ self.assertEqual(f.as_table(), """<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" id="id_name" /></td></tr>
+<tr><th><label for="id_language_0">Language:</label></th><td><ul>
+<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul></td></tr>""")
+ self.assertEqual(f.as_ul(), """<li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
+<li><label for="id_language_0">Language:</label> <ul>
+<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul></li>""")
+ self.assertEqual(f.as_p(), """<p><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
+<p><label for="id_language_0">Language:</label> <ul>
+<li><label for="id_language_0"><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label for="id_language_1"><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul></p>""")
+
+ def test_forms_wit_hmultiple_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.assertEqual(str(f['composers']), """<select multiple="multiple" name="composers">
+</select>""")
+
+ class SongForm(Form):
+ name = CharField()
+ composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')])
+
+ f = SongForm(auto_id=False)
+ self.assertEqual(str(f['composers']), """<select multiple="multiple" name="composers">
+<option value="J">John Lennon</option>
+<option value="P">Paul McCartney</option>
+</select>""")
+ f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
+ self.assertEqual(str(f['name']), '<input type="text" name="name" value="Yesterday" />')
+ self.assertEqual(str(f['composers']), """<select multiple="multiple" name="composers">
+<option value="J">John Lennon</option>
+<option value="P" selected="selected">Paul McCartney</option>
+</select>""")
+
+ 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 <input type="hidden">
+ # tags.
+ f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
+ self.assertEqual(f['composers'].as_hidden(), '<input type="hidden" name="composers" value="P" />')
+ f = SongForm({'name': 'From Me To You', 'composers': ['P', 'J']}, auto_id=False)
+ self.assertEqual(f['composers'].as_hidden(), """<input type="hidden" name="composers" value="P" />
+<input type="hidden" name="composers" value="J" />""")
+
+ 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.assertEqual(str(f['composers']), """<ul>
+<li><label><input type="checkbox" name="composers" value="J" /> John Lennon</label></li>
+<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
+</ul>""")
+ f = SongForm({'composers': ['J']}, auto_id=False)
+ self.assertEqual(str(f['composers']), """<ul>
+<li><label><input checked="checked" type="checkbox" name="composers" value="J" /> John Lennon</label></li>
+<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
+</ul>""")
+ f = SongForm({'composers': ['J', 'P']}, auto_id=False)
+ self.assertEqual(str(f['composers']), """<ul>
+<li><label><input checked="checked" type="checkbox" name="composers" value="J" /> John Lennon</label></li>
+<li><label><input checked="checked" type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
+</ul>""")
+
+ 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.assertEqual(str(f['composers']), """<ul>
+<li><label for="composers_id_0"><input type="checkbox" name="composers" value="J" id="composers_id_0" /> John Lennon</label></li>
+<li><label for="composers_id_1"><input type="checkbox" name="composers" value="P" id="composers_id_1" /> Paul McCartney</label></li>
+</ul>""")
+
+ 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.assertEqual(f.as_ul(), """<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
+<input type="hidden" name="composers" value="P" /></li>""")
+
+ # 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'], [u'This field is required.'])
+ f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
+ self.assertEqual(f.errors, {})
+ self.assertEqual(f.cleaned_data['composers'], [u'J'])
+ self.assertEqual(f.cleaned_data['name'], u'Yesterday')
+ f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
+ self.assertEqual(f.errors, {})
+ self.assertEqual(f.cleaned_data['composers'], [u'J', u'P'])
+ self.assertEqual(f.cleaned_data['name'], u'Yesterday')
+
+ def test_escaping(self):
+ # Validation errors are HTML-escaped when output as HTML.
+ class EscapingForm(Form):
+ special_name = CharField(label="<em>Special</em> Field")
+ special_safe_name = CharField(label=mark_safe("<em>Special</em> 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("'<b>%s</b>' 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.assertEqual(f.as_table(), """<tr><th>&lt;em&gt;Special&lt;/em&gt; Field:</th><td><ul class="errorlist"><li>Something&#39;s wrong with &#39;Nothing to escape&#39;</li></ul><input type="text" name="special_name" value="Nothing to escape" /></td></tr>
+<tr><th><em>Special</em> Field:</th><td><ul class="errorlist"><li>'<b>Nothing to escape</b>' is a safe string</li></ul><input type="text" name="special_safe_name" value="Nothing to escape" /></td></tr>""")
+ f = EscapingForm({
+ 'special_name': "Should escape < & > and <script>alert('xss')</script>",
+ 'special_safe_name': "<i>Do not escape</i>"
+ }, auto_id=False)
+ self.assertEqual(f.as_table(), """<tr><th>&lt;em&gt;Special&lt;/em&gt; Field:</th><td><ul class="errorlist"><li>Something&#39;s wrong with &#39;Should escape &lt; &amp; &gt; and &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#39;</li></ul><input type="text" name="special_name" value="Should escape &lt; &amp; &gt; and &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;" /></td></tr>
+<tr><th><em>Special</em> Field:</th><td><ul class="errorlist"><li>'<b><i>Do not escape</i></b>' is a safe string</li></ul><input type="text" name="special_safe_name" value="&lt;i&gt;Do not escape&lt;/i&gt;" /></td></tr>""")
+
+ 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(u'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'], [u'This field is required.'])
+ self.assertEqual(f.errors['password1'], [u'This field is required.'])
+ self.assertEqual(f.errors['password2'], [u'This field is required.'])
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
+ self.assertEqual(f.errors['password2'], [u'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'], u'adrian')
+ self.assertEqual(f.cleaned_data['password1'], u'foo')
+ self.assertEqual(f.cleaned_data['password2'], u'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(u'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.assertEqual(f.as_table(), """<tr><th>Username:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="username" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password1" /></td></tr>
+<tr><th>Password2:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password2" /></td></tr>""")
+ self.assertEqual(f.errors['username'], [u'This field is required.'])
+ self.assertEqual(f.errors['password1'], [u'This field is required.'])
+ self.assertEqual(f.errors['password2'], [u'This field is required.'])
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
+ self.assertEqual(f.errors['__all__'], [u'Please make sure your passwords match.'])
+ self.assertEqual(f.as_table(), """<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
+<tr><th>Username:</th><td><input type="text" name="username" value="adrian" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr>
+<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr>""")
+ self.assertEqual(f.as_ul(), """<li><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></li>
+<li>Username: <input type="text" name="username" value="adrian" maxlength="10" /></li>
+<li>Password1: <input type="password" name="password1" value="foo" /></li>
+<li>Password2: <input type="password" name="password2" value="bar" /></li>""")
+ f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
+ self.assertEqual(f.errors, {})
+ self.assertEqual(f.cleaned_data['username'], u'adrian')
+ self.assertEqual(f.cleaned_data['password1'], u'foo')
+ self.assertEqual(f.cleaned_data['password2'], u'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.assertEqual(p.as_table(), """<tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
+<tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>""")
+
+ # 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.assertEqual(my_form.as_table(), """<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr>
+<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>""")
+ field_list = [('field3', CharField()), ('field4', CharField())]
+ my_form = MyForm(field_list=field_list)
+ self.assertEqual(my_form.as_table(), """<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr>
+<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>""")
+
+ class MyForm(Form):
+ default_field_1 = CharField()
+ default_field_2 = CharField()
+
+ 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.assertEqual(my_form.as_table(), """<tr><th>Default field 1:</th><td><input type="text" name="default_field_1" /></td></tr>
+<tr><th>Default field 2:</th><td><input type="text" name="default_field_2" /></td></tr>
+<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr>
+<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>""")
+ field_list = [('field3', CharField()), ('field4', CharField())]
+ my_form = MyForm(field_list=field_list)
+ self.assertEqual(my_form.as_table(), """<tr><th>Default field 1:</th><td><input type="text" name="default_field_1" /></td></tr>
+<tr><th>Default field 2:</th><td><input type="text" name="default_field_2" /></td></tr>
+<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr>
+<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>""")
+
+ # 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))
+
+ 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.assertEqual(p.as_table(), """<tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
+<tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>""")
+ self.assertEqual(p.as_ul(), """<li>First name: <input type="text" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>""")
+ self.assertEqual(p.as_p(), """<p>First name: <input type="text" name="first_name" /></p>
+<p>Last name: <input type="text" name="last_name" /></p>
+<p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>""")
+
+ # With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
+ p = Person(auto_id='id_%s')
+ self.assertEqual(p.as_table(), """<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>""")
+ self.assertEqual(p.as_ul(), """<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>""")
+ self.assertEqual(p.as_p(), """<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>""")
+
+ # 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.assertEqual(p.as_table(), """<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
+<tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
+<tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>""")
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
+<li>First name: <input type="text" name="first_name" value="John" /></li>
+<li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
+<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>""")
+ self.assertEqual(p.as_p(), """<ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
+<p>First name: <input type="text" name="first_name" value="John" /></p>
+<p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
+<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>""")
+
+ # 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.assertEqual(p.as_table(), '<input type="hidden" name="foo" /><input type="hidden" name="bar" />')
+ self.assertEqual(p.as_ul(), '<input type="hidden" name="foo" /><input type="hidden" name="bar" />')
+ self.assertEqual(p.as_p(), '<input type="hidden" name="foo" /><input type="hidden" name="bar" />')
+
+ 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.assertEqual(p.as_table(), """<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr>
+<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
+<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr>
+<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
+<tr><th>Field5:</th><td><input type="text" name="field5" /></td></tr>
+<tr><th>Field6:</th><td><input type="text" name="field6" /></td></tr>
+<tr><th>Field7:</th><td><input type="text" name="field7" /></td></tr>
+<tr><th>Field8:</th><td><input type="text" name="field8" /></td></tr>
+<tr><th>Field9:</th><td><input type="text" name="field9" /></td></tr>
+<tr><th>Field10:</th><td><input type="text" name="field10" /></td></tr>
+<tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr>
+<tr><th>Field12:</th><td><input type="text" name="field12" /></td></tr>
+<tr><th>Field13:</th><td><input type="text" name="field13" /></td></tr>
+<tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr>""")
+
+ 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" maxlength="10" /></li>
+<li>Realname: <input type="text" name="realname" maxlength="10" /></li>
+<li>Address: <input type="text" name="address" /></li>""")
+
+ # 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" maxlength="10" /></li>""")
+
+ 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.assertEqual(p.as_ul(), """<li>Your username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password1: <input type="password" name="password1" /></li>
+<li>Password (again): <input type="password" name="password2" /></li>""")
+
+ # 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.assertEqual(Questions(auto_id=False).as_p(), """<p>The first question: <input type="text" name="q1" /></p>
+<p>What is your name? <input type="text" name="q2" /></p>
+<p>The answer to life is: <input type="text" name="q3" /></p>
+<p>Answer this question! <input type="text" name="q4" /></p>
+<p>The last question. Period. <input type="text" name="q5" /></p>""")
+ self.assertEqual(Questions().as_p(), """<p><label for="id_q1">The first question:</label> <input type="text" name="q1" id="id_q1" /></p>
+<p><label for="id_q2">What is your name?</label> <input type="text" name="q2" id="id_q2" /></p>
+<p><label for="id_q3">The answer to life is:</label> <input type="text" name="q3" id="id_q3" /></p>
+<p><label for="id_q4">Answer this question!</label> <input type="text" name="q4" id="id_q4" /></p>
+<p><label for="id_q5">The last question. Period.</label> <input type="text" name="q5" id="id_q5" /></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=u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
+
+ p = UserRegistration(auto_id=False)
+ self.assertEqual(p.as_ul(), u'<li>\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111: <input type="text" name="username" maxlength="10" /></li>\n<li>\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111: <input type="password" name="password" /></li>')
+
+ # 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.assertEqual(p.as_ul(), """<li> <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration(auto_id='id_%s')
+ self.assertEqual(p.as_ul(), """<li> <input id="id_username" type="text" name="username" maxlength="10" /></li>
+<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>""")
+
+ # 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration(auto_id='id_%s')
+ self.assertEqual(p.as_ul(), """<li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li>
+<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>""")
+
+ 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.assertEqual(f.as_ul(), """<li>Favorite color? <input type="text" name="color" /></li>
+<li>Favorite animal: <input type="text" name="animal" /></li>""")
+ f = FavoriteForm(auto_id=False, label_suffix='?')
+ self.assertEqual(f.as_ul(), """<li>Favorite color? <input type="text" name="color" /></li>
+<li>Favorite animal? <input type="text" name="animal" /></li>""")
+ f = FavoriteForm(auto_id=False, label_suffix='')
+ self.assertEqual(f.as_ul(), """<li>Favorite color? <input type="text" name="color" /></li>
+<li>Favorite animal <input type="text" name="animal" /></li>""")
+ f = FavoriteForm(auto_id=False, label_suffix=u'\u2192')
+ self.assertEqual(f.as_ul(), u'<li>Favorite color? <input type="text" name="color" /></li>\n<li>Favorite animal\u2192 <input type="text" name="animal" /></li>')
+
+ 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>""")
+
+ # Here, we're submitting data, so the initial value will *not* be displayed.
+ p = UserRegistration({}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration({'username': u''}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration({'username': u'foo'}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>""")
+
+ # 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'], [u'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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration(initial={'username': 'stephane'}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>""")
+
+ # The 'initial' parameter is meaningless if you pass data.
+ p = UserRegistration({}, initial={'username': 'django'}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>""")
+ p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>""")
+
+ # 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'], [u'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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="babik" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>""")
+
+ 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+<li>Options: <select multiple="multiple" name="options">
+<option value="f" selected="selected">foo</option>
+<option value="b" selected="selected">bar</option>
+<option value="w">whiz</option>
+</select></li>""")
+
+ # The 'initial' parameter is meaningless if you pass data.
+ p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Options: <select multiple="multiple" name="options">
+<option value="f">foo</option>
+<option value="b">bar</option>
+<option value="w">whiz</option>
+</select></li>""")
+ p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Options: <select multiple="multiple" name="options">
+<option value="f">foo</option>
+<option value="b">bar</option>
+<option value="w">whiz</option>
+</select></li>""")
+ p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+<li>Options: <select multiple="multiple" name="options">
+<option value="f" selected="selected">foo</option>
+<option value="b" selected="selected">bar</option>
+<option value="w">whiz</option>
+</select></li>""")
+
+ # 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'], [u'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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+<li>Options: <select multiple="multiple" name="options">
+<option value="f">foo</option>
+<option value="b" selected="selected">bar</option>
+<option value="w" selected="selected">whiz</option>
+</select></li>""")
+ p = UserRegistration(initial={'username': initial_stephane, 'options': initial_options}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+<li>Options: <select multiple="multiple" name="options">
+<option value="f" selected="selected">foo</option>
+<option value="b" selected="selected">bar</option>
+<option value="w">whiz</option>
+</select></li>""")
+
+ 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
+<li>Password: <input type="password" name="password" /> Choose wisely.</li>""")
+ self.assertEqual(p.as_p(), """<p>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</p>
+<p>Password: <input type="password" name="password" /> Choose wisely.</p>""")
+ self.assertEqual(p.as_table(), """<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /><br />e.g., user@example.com</td></tr>
+<tr><th>Password:</th><td><input type="password" name="password" /><br />Choose wisely.</td></tr>""")
+
+ # The help text is displayed whether or not data is provided for the form.
+ p = UserRegistration({'username': u'foo'}, auto_id=False)
+ self.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /> e.g., user@example.com</li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /> Choose wisely.</li>""")
+
+ # 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.assertEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
+<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>""")
+
+ # Help text can include arbitrary Unicode characters.
+ class UserRegistration(Form):
+ username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ')
+
+ p = UserRegistration(auto_id=False)
+ self.assertEqual(p.as_ul(), u'<li>Username: <input type="text" name="username" maxlength="10" /> \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</li>')
+
+ 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.assertEqual(p.as_ul(), """<li>First name: <input type="text" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>""")
+ m = Musician(auto_id=False)
+ self.assertEqual(m.as_ul(), """<li>First name: <input type="text" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>
+<li>Instrument: <input type="text" name="instrument" /></li>""")
+
+ # 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.assertEqual(b.as_ul(), """<li>First name: <input type="text" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>
+<li>Instrument: <input type="text" name="instrument" /></li>
+<li>Haircut type: <input type="text" name="haircut_type" /></li>""")
+
+ 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': u'John',
+ 'person1-last_name': u'Lennon',
+ 'person1-birthday': u'1940-10-9'
+ }
+ p = Person(data, prefix='person1')
+ self.assertEqual(p.as_ul(), """<li><label for="id_person1-first_name">First name:</label> <input type="text" name="person1-first_name" value="John" id="id_person1-first_name" /></li>
+<li><label for="id_person1-last_name">Last name:</label> <input type="text" name="person1-last_name" value="Lennon" id="id_person1-last_name" /></li>
+<li><label for="id_person1-birthday">Birthday:</label> <input type="text" name="person1-birthday" value="1940-10-9" id="id_person1-birthday" /></li>""")
+ self.assertEqual(str(p['first_name']), '<input type="text" name="person1-first_name" value="John" id="id_person1-first_name" />')
+ self.assertEqual(str(p['last_name']), '<input type="text" name="person1-last_name" value="Lennon" id="id_person1-last_name" />')
+ self.assertEqual(str(p['birthday']), '<input type="text" name="person1-birthday" value="1940-10-9" id="id_person1-birthday" />')
+ self.assertEqual(p.errors, {})
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.cleaned_data['first_name'], u'John')
+ self.assertEqual(p.cleaned_data['last_name'], u'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': u'',
+ 'person1-last_name': u'',
+ 'person1-birthday': u''
+ }
+ p = Person(data, prefix='person1')
+ self.assertEqual(p.errors['first_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['last_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['birthday'], [u'This field is required.'])
+ self.assertEqual(p['first_name'].errors, [u'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': u'John',
+ 'last_name': u'Lennon',
+ 'birthday': u'1940-10-9'
+ }
+ p = Person(data, prefix='person1')
+ self.assertEqual(p.errors['first_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['last_name'], [u'This field is required.'])
+ self.assertEqual(p.errors['birthday'], [u'This field is required.'])
+
+ # With prefixes, a single data dictionary can hold data for multiple instances
+ # of the same form.
+ data = {
+ 'person1-first_name': u'John',
+ 'person1-last_name': u'Lennon',
+ 'person1-birthday': u'1940-10-9',
+ 'person2-first_name': u'Jim',
+ 'person2-last_name': u'Morrison',
+ 'person2-birthday': u'1943-12-8'
+ }
+ p1 = Person(data, prefix='person1')
+ self.assertTrue(p1.is_valid())
+ self.assertEqual(p1.cleaned_data['first_name'], u'John')
+ self.assertEqual(p1.cleaned_data['last_name'], u'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'], u'Jim')
+ self.assertEqual(p2.cleaned_data['last_name'], u'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.assertEqual(p.as_ul(), """<li><label for="id_foo-prefix-first_name">First name:</label> <input type="text" name="foo-prefix-first_name" id="id_foo-prefix-first_name" /></li>
+<li><label for="id_foo-prefix-last_name">Last name:</label> <input type="text" name="foo-prefix-last_name" id="id_foo-prefix-last_name" /></li>
+<li><label for="id_foo-prefix-birthday">Birthday:</label> <input type="text" name="foo-prefix-birthday" id="id_foo-prefix-birthday" /></li>""")
+ data = {
+ 'foo-prefix-first_name': u'John',
+ 'foo-prefix-last_name': u'Lennon',
+ 'foo-prefix-birthday': u'1940-10-9'
+ }
+ p = Person(data, prefix='foo')
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.cleaned_data['first_name'], u'John')
+ self.assertEqual(p.cleaned_data['last_name'], u'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': u'Joe'}, auto_id=False)
+ self.assertEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>""")
+ p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False)
+ self.assertEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>""")
+ p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False)
+ self.assertEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>""")
+ p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False)
+ self.assertEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>""")
+ p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False)
+ self.assertEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>""")
+ p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False)
+ self.assertEqual(str(p['is_cool']), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>""")
+
+ 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.assertEqual(f.as_table(), '<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>')
+
+ f = FileForm(data={}, files={}, auto_id=False)
+ self.assertEqual(f.as_table(), '<tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>')
+
+ f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
+ self.assertEqual(f.as_table(), '<tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>')
+
+ f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False)
+ self.assertEqual(f.as_table(), '<tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>')
+
+ f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False)
+ self.assertEqual(f.as_table(), '<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>')
+ self.assertTrue(f.is_valid())
+
+ f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False)
+ self.assertEqual(f.as_table(), '<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>')
+
+ 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(u'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' % form.cleaned_data
+
+ t = Template('<form action="" method="post">\n<table>\n{{ form }}\n</table>\n<input type="submit" />\n</form>')
+ return t.render(Context({'form': form}))
+
+ # Case 1: GET (an empty form, with no errors).)
+ self.assertEqual(my_function('GET', {}), """<form action="" method="post">
+<table>
+<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><input type="password" name="password1" /></td></tr>
+<tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr>
+</table>
+<input type="submit" />
+</form>""")
+ # Case 2: POST with erroneous data (a redisplayed form, with errors).)
+ self.assertEqual(my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'}), """<form action="" method="post">
+<table>
+<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
+<tr><th>Username:</th><td><ul class="errorlist"><li>Ensure this value has at most 10 characters (it has 23).</li></ul><input type="text" name="username" value="this-is-a-long-username" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr>
+<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr>
+</table>
+<input type="submit" />
+</form>""")
+ # Case 3: POST with valid data (the success message).)
+ self.assertEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}), "VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'}")
+
+ 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(u'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('''<form action="">
+{{ form.username.errors.as_ul }}<p><label>Your username: {{ form.username }}</label></p>
+{{ form.password1.errors.as_ul }}<p><label>Password: {{ form.password1 }}</label></p>
+{{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
+<input type="submit" />
+</form>''')
+ self.assertEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action="">
+<p><label>Your username: <input type="text" name="username" maxlength="10" /></label></p>
+<p><label>Password: <input type="password" name="password1" /></label></p>
+<p><label>Password (again): <input type="password" name="password2" /></label></p>
+<input type="submit" />
+</form>""")
+ self.assertEqual(t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)})), """<form action="">
+<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p>
+<ul class="errorlist"><li>This field is required.</li></ul><p><label>Password: <input type="password" name="password1" /></label></p>
+<ul class="errorlist"><li>This field is required.</li></ul><p><label>Password (again): <input type="password" name="password2" /></label></p>
+<input type="submit" />
+</form>""")
+
+ # 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('''<form action="">
+<p><label>{{ form.username.label }}: {{ form.username }}</label></p>
+<p><label>{{ form.password1.label }}: {{ form.password1 }}</label></p>
+<p><label>{{ form.password2.label }}: {{ form.password2 }}</label></p>
+<input type="submit" />
+</form>''')
+ self.assertEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action="">
+<p><label>Username: <input type="text" name="username" maxlength="10" /></label></p>
+<p><label>Password1: <input type="password" name="password1" /></label></p>
+<p><label>Password2: <input type="password" name="password2" /></label></p>
+<input type="submit" />
+</form>""")
+
+ # User form.[field].label_tag to output a field's label with a <label> 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('''<form action="">
+<p>{{ form.username.label_tag }}: {{ form.username }}</p>
+<p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
+<p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
+<input type="submit" />
+</form>''')
+ self.assertEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action="">
+<p>Username: <input type="text" name="username" maxlength="10" /></p>
+<p>Password1: <input type="password" name="password1" /></p>
+<p>Password2: <input type="password" name="password2" /></p>
+<input type="submit" />
+</form>""")
+ self.assertEqual(t.render(Context({'form': UserRegistration(auto_id='id_%s')})), """<form action="">
+<p><label for="id_username">Username</label>: <input id="id_username" type="text" name="username" maxlength="10" /></p>
+<p><label for="id_password1">Password1</label>: <input type="password" name="password1" id="id_password1" /></p>
+<p><label for="id_password2">Password2</label>: <input type="password" name="password2" id="id_password2" /></p>
+<input type="submit" />
+</form>""")
+
+ # 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('''<form action="">
+<p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p>
+<p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
+<p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
+<input type="submit" />
+</form>''')
+ self.assertEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """<form action="">
+<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn&#39;t already exist.</p>
+<p>Password1: <input type="password" name="password1" /></p>
+<p>Password2: <input type="password" name="password2" /></p>
+<input type="submit" />
+</form>""")
+ self.assertEqual(Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})), u'')
+
+ # The label_tag() method takes an optional attrs argument: a dictionary of HTML
+ # attributes to add to the <label> tag.
+ f = UserRegistration(auto_id='id_%s')
+ form_output = []
+
+ for bf in f:
+ form_output.append(bf.label_tag(attrs={'class': 'pretty'}))
+
+ self.assertEqual(form_output, [
+ '<label for="id_username" class="pretty">Username</label>',
+ '<label for="id_password1" class="pretty">Password1</label>',
+ '<label for="id_password2" class="pretty">Password2</label>',
+ ])
+
+ # 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 <ul> (or an empty string, if
+ # the list of errors is empty). You can also use it in {% if %} statements.
+ t = Template('''<form action="">
+{{ form.username.errors.as_ul }}<p><label>Your username: {{ form.username }}</label></p>
+{{ form.password1.errors.as_ul }}<p><label>Password: {{ form.password1 }}</label></p>
+{{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
+<input type="submit" />
+</form>''')
+ self.assertEqual(t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})), """<form action="">
+<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p>
+<p><label>Password: <input type="password" name="password1" value="foo" /></label></p>
+<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
+<input type="submit" />
+</form>""")
+ t = Template('''<form action="">
+{{ form.non_field_errors }}
+{{ form.username.errors.as_ul }}<p><label>Your username: {{ form.username }}</label></p>
+{{ form.password1.errors.as_ul }}<p><label>Password: {{ form.password1 }}</label></p>
+{{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
+<input type="submit" />
+</form>''')
+ self.assertEqual(t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})), """<form action="">
+<ul class="errorlist"><li>Please make sure your passwords match.</li></ul>
+<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p>
+<p><label>Password: <input type="password" name="password1" value="foo" /></label></p>
+<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
+<input type="submit" />
+</form>""")
+
+ 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': [u'This field is required.'], 'artist': [u'This field is required.']})
+ try:
+ form.cleaned_data
+ self.fail('Attempts to access cleaned_data when validation fails should fail.')
+ except AttributeError:
+ pass
+
+ # 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': [u'This field is required.']})
+ try:
+ form.cleaned_data
+ self.fail('Attempts to access cleaned_data when validation fails should fail.')
+ except AttributeError:
+ pass
+
+ # 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.assertEqual(MyForm().as_table(), '<tr><th><label for="id_field1">Field1:</label></th><td><input id="id_field1" type="text" name="field1" maxlength="50" /><input type="hidden" name="initial-field1" id="initial-id_field1" /></td></tr>')
+
+ def test_error_html_required_html_classes(self):
+ class Person(Form):
+ name = CharField()
+ is_cool = NullBooleanField()
+ email = EmailField(required=False)
+ age = IntegerField()
+
+ p = Person({})
+ p.error_css_class = 'error'
+ p.required_css_class = 'required'
+
+ self.assertEqual(p.as_ul(), """<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
+<li class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select></li>
+<li><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></li>
+<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></li>""")
+
+ self.assertEqual(p.as_p(), """<ul class="errorlist"><li>This field is required.</li></ul>
+<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
+<p class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select></p>
+<p><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></p>
+<ul class="errorlist"><li>This field is required.</li></ul>
+<p class="required error"><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p>""")
+
+ self.assertEqual(p.as_table(), """<tr class="required error"><th><label for="id_name">Name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="name" id="id_name" /></td></tr>
+<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select></td></tr>
+<tr><th><label for="id_email">Email:</label></th><td><input type="text" name="email" id="id_email" /></td></tr>
+<tr class="required error"><th><label for="id_age">Age:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="age" id="id_age" /></td></tr>""")
+
+ def test_label_split_datetime_not_displayed(self):
+ class EventForm(Form):
+ happened_at = SplitDateTimeField(widget=widgets.SplitHiddenDateTimeWidget)
+
+ form = EventForm()
+ self.assertEqual(form.as_ul(), u'<input type="hidden" name="happened_at_0" id="id_happened_at_0" /><input type="hidden" name="happened_at_1" id="id_happened_at_1" />')
diff --git a/parts/django/tests/regressiontests/forms/tests/formsets.py b/parts/django/tests/regressiontests/forms/tests/formsets.py
new file mode 100644
index 0000000..db94797
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/formsets.py
@@ -0,0 +1,763 @@
+# -*- coding: utf-8 -*-
+from unittest import TestCase
+from django.forms import Form, CharField, IntegerField, ValidationError
+from django.forms.formsets import formset_factory, BaseFormSet
+
+
+class Choice(Form):
+ choice = CharField()
+ votes = IntegerField()
+
+
+# FormSet allows us to use multiple instance of the same form on 1 page. For now,
+# the best way to create a FormSet is by using the formset_factory function.
+ChoiceFormSet = formset_factory(Choice)
+
+
+class FavoriteDrinkForm(Form):
+ name = CharField()
+
+
+class BaseFavoriteDrinksFormSet(BaseFormSet):
+ def clean(self):
+ seen_drinks = []
+
+ for drink in self.cleaned_data:
+ if drink['name'] in seen_drinks:
+ raise ValidationError('You may only specify a drink once.')
+
+ seen_drinks.append(drink['name'])
+
+
+# Let's define a FormSet that takes a list of favorite drinks, but raises an
+# error if there are any duplicates. Used in ``test_clean_hook``,
+# ``test_regression_6926`` & ``test_regression_12878``.
+FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
+ formset=BaseFavoriteDrinksFormSet, extra=3)
+
+
+class FormsFormsetTestCase(TestCase):
+ def test_basic_formset(self):
+ # A FormSet constructor takes the same arguments as Form. Let's create a FormSet
+ # for adding data. By default, it displays 1 blank form. It can display more,
+ # but we'll look at how to do so later.
+ formset = ChoiceFormSet(auto_id=False, prefix='choices')
+ self.assertEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" />
+<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
+<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""")
+
+ # On thing to note is that there needs to be a special value in the data. This
+ # value tells the FormSet how many forms were displayed so it can tell how
+ # many forms it needs to clean and validate. You could use javascript to create
+ # new forms on the client side, but they won't get validated unless you increment
+ # the TOTAL_FORMS field appropriately.
+
+ data = {
+ 'choices-TOTAL_FORMS': '1', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ }
+ # We treat FormSet pretty much like we would treat a normal Form. FormSet has an
+ # is_valid method, and a cleaned_data or errors attribute depending on whether all
+ # the forms passed validation. However, unlike a Form instance, cleaned_data and
+ # errors will be a list of dicts rather than just a single dict.
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}])
+
+ # If a FormSet was not passed any data, its is_valid method should return False.
+ formset = ChoiceFormSet()
+ self.assertFalse(formset.is_valid())
+
+ def test_formset_validation(self):
+ # FormSet instances can also have an error attribute if validation failed for
+ # any of the forms.
+
+ data = {
+ 'choices-TOTAL_FORMS': '1', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(formset.is_valid())
+ self.assertEqual(formset.errors, [{'votes': [u'This field is required.']}])
+
+ def test_formset_initial_data(self):
+ # We can also prefill a FormSet with existing data by providing an ``initial``
+ # argument to the constructor. ``initial`` should be a list of dicts. By default,
+ # an extra blank form is included.
+
+ initial = [{'choice': u'Calexico', 'votes': 100}]
+ formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(form.as_ul())
+
+ self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" /></li>""")
+
+ # Let's simulate what would happen if we submitted this form.
+
+ data = {
+ 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-1-choice': '',
+ 'choices-1-votes': '',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}])
+
+ def test_second_form_partially_filled(self):
+ # But the second form was blank! Shouldn't we get some errors? No. If we display
+ # a form as blank, it's ok for it to be submitted as blank. If we fill out even
+ # one of the fields of a blank form though, it will be validated. We may want to
+ # required that at least x number of forms are completed, but we'll show how to
+ # handle that later.
+
+ data = {
+ 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-1-choice': 'The Decemberists',
+ 'choices-1-votes': '', # missing value
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(formset.is_valid())
+ self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}])
+
+ def test_delete_prefilled_data(self):
+ # If we delete data that was pre-filled, we should get an error. Simply removing
+ # data from form fields isn't the proper way to delete it. We'll see how to
+ # handle that case later.
+
+ data = {
+ 'choices-TOTAL_FORMS': '2', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': '', # deleted value
+ 'choices-0-votes': '', # deleted value
+ 'choices-1-choice': '',
+ 'choices-1-votes': '',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(formset.is_valid())
+ self.assertEqual(formset.errors, [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}])
+
+ def test_displaying_more_than_one_blank_form(self):
+ # Displaying more than 1 blank form ###########################################
+ # We can also display more than 1 empty form at a time. To do so, pass a
+ # extra argument to formset_factory.
+ ChoiceFormSet = formset_factory(Choice, extra=3)
+
+ formset = ChoiceFormSet(auto_id=False, prefix='choices')
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(form.as_ul())
+
+ self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>""")
+
+ # Since we displayed every form as blank, we will also accept them back as blank.
+ # This may seem a little strange, but later we will show how to require a minimum
+ # number of forms to be completed.
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': '',
+ 'choices-0-votes': '',
+ 'choices-1-choice': '',
+ 'choices-1-votes': '',
+ 'choices-2-choice': '',
+ 'choices-2-votes': '',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertEqual([form.cleaned_data for form in formset.forms], [{}, {}, {}])
+
+ def test_single_form_completed(self):
+ # We can just fill out one of the forms.
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-1-choice': '',
+ 'choices-1-votes': '',
+ 'choices-2-choice': '',
+ 'choices-2-votes': '',
+ }
+
+ ChoiceFormSet = formset_factory(Choice, extra=3)
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}, {}])
+
+ def test_second_form_partially_filled_2(self):
+ # And once again, if we try to partially complete a form, validation will fail.
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-1-choice': 'The Decemberists',
+ 'choices-1-votes': '', # missing value
+ 'choices-2-choice': '',
+ 'choices-2-votes': '',
+ }
+
+ ChoiceFormSet = formset_factory(Choice, extra=3)
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertFalse(formset.is_valid())
+ self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}, {}])
+
+ def test_more_initial_data(self):
+ # The extra argument also works when the formset is pre-filled with initial
+ # data.
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-1-choice': '',
+ 'choices-1-votes': '', # missing value
+ 'choices-2-choice': '',
+ 'choices-2-votes': '',
+ }
+
+ initial = [{'choice': u'Calexico', 'votes': 100}]
+ ChoiceFormSet = formset_factory(Choice, extra=3)
+ formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(form.as_ul())
+
+ self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+<li>Choice: <input type="text" name="choices-3-choice" /></li>
+<li>Votes: <input type="text" name="choices-3-votes" /></li>""")
+
+ # Make sure retrieving an empty form works, and it shows up in the form list
+
+ self.assertTrue(formset.empty_form.empty_permitted)
+ self.assertEqual(formset.empty_form.as_ul(), """<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
+<li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>""")
+
+ def test_formset_with_deletion(self):
+ # FormSets with deletion ######################################################
+ # We can easily add deletion ability to a FormSet with an argument to
+ # formset_factory. This will add a boolean field to each form instance. When
+ # that boolean field is True, the form will be in formset.deleted_forms
+
+ ChoiceFormSet = formset_factory(Choice, can_delete=True)
+
+ initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
+ formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(form.as_ul())
+
+ self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
+<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>""")
+
+ # To delete something, we just need to set that form's special delete field to
+ # 'on'. Let's go ahead and delete Fergie.
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-0-DELETE': '',
+ 'choices-1-choice': 'Fergie',
+ 'choices-1-votes': '900',
+ 'choices-1-DELETE': 'on',
+ 'choices-2-choice': '',
+ 'choices-2-votes': '',
+ 'choices-2-DELETE': '',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}])
+ self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}])
+
+ # If we fill a form with something and then we check the can_delete checkbox for
+ # that form, that form's errors should not make the entire formset invalid since
+ # it's going to be deleted.
+
+ class CheckForm(Form):
+ field = IntegerField(min_value=100)
+
+ data = {
+ 'check-TOTAL_FORMS': '3', # the number of forms rendered
+ 'check-INITIAL_FORMS': '2', # the number of forms with initial data
+ 'check-MAX_NUM_FORMS': '0', # max number of forms
+ 'check-0-field': '200',
+ 'check-0-DELETE': '',
+ 'check-1-field': '50',
+ 'check-1-DELETE': 'on',
+ 'check-2-field': '',
+ 'check-2-DELETE': '',
+ }
+ CheckFormSet = formset_factory(CheckForm, can_delete=True)
+ formset = CheckFormSet(data, prefix='check')
+ self.assertTrue(formset.is_valid())
+
+ # If we remove the deletion flag now we will have our validation back.
+ data['check-1-DELETE'] = ''
+ formset = CheckFormSet(data, prefix='check')
+ self.assertFalse(formset.is_valid())
+
+ # Should be able to get deleted_forms from a valid formset even if a
+ # deleted form would have been invalid.
+
+ class Person(Form):
+ name = CharField()
+
+ PeopleForm = formset_factory(
+ form=Person,
+ can_delete=True)
+
+ p = PeopleForm(
+ {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
+ 'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
+ 'form-MAX_NUM_FORMS': 1})
+
+ self.assertTrue(p.is_valid())
+ self.assertEqual(len(p.deleted_forms), 1)
+
+ def test_formsets_with_ordering(self):
+ # FormSets with ordering ######################################################
+ # We can also add ordering ability to a FormSet with an argument to
+ # formset_factory. This will add a integer field to each form instance. When
+ # form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
+ # order specified by the ordering fields. If a number is duplicated in the set
+ # of ordering fields, for instance form 0 and form 3 are both marked as 1, then
+ # the form index used as a secondary ordering criteria. In order to put
+ # something at the front of the list, you'd need to set it's order to 0.
+
+ ChoiceFormSet = formset_factory(Choice, can_order=True)
+
+ initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
+ formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(form.as_ul())
+
+ self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
+<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" /></li>
+<li>Order: <input type="text" name="choices-2-ORDER" /></li>""")
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-0-ORDER': '1',
+ 'choices-1-choice': 'Fergie',
+ 'choices-1-votes': '900',
+ 'choices-1-ORDER': '2',
+ 'choices-2-choice': 'The Decemberists',
+ 'choices-2-votes': '500',
+ 'choices-2-ORDER': '0',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ form_output = []
+
+ for form in formset.ordered_forms:
+ form_output.append(form.cleaned_data)
+
+ self.assertEqual(form_output, [
+ {'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'},
+ {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
+ {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
+ ])
+
+ def test_empty_ordered_fields(self):
+ # Ordering fields are allowed to be left blank, and if they *are* left blank,
+ # they will be sorted below everything else.
+
+ data = {
+ 'choices-TOTAL_FORMS': '4', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-0-ORDER': '1',
+ 'choices-1-choice': 'Fergie',
+ 'choices-1-votes': '900',
+ 'choices-1-ORDER': '2',
+ 'choices-2-choice': 'The Decemberists',
+ 'choices-2-votes': '500',
+ 'choices-2-ORDER': '',
+ 'choices-3-choice': 'Basia Bulat',
+ 'choices-3-votes': '50',
+ 'choices-3-ORDER': '',
+ }
+
+ ChoiceFormSet = formset_factory(Choice, can_order=True)
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ form_output = []
+
+ for form in formset.ordered_forms:
+ form_output.append(form.cleaned_data)
+
+ self.assertEqual(form_output, [
+ {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
+ {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
+ {'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'},
+ {'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'},
+ ])
+
+ def test_ordering_blank_fieldsets(self):
+ # Ordering should work with blank fieldsets.
+
+ data = {
+ 'choices-TOTAL_FORMS': '3', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ }
+
+ ChoiceFormSet = formset_factory(Choice, can_order=True)
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ form_output = []
+
+ for form in formset.ordered_forms:
+ form_output.append(form.cleaned_data)
+
+ self.assertEqual(form_output, [])
+
+ def test_formset_with_ordering_and_deletion(self):
+ # FormSets with ordering + deletion ###########################################
+ # Let's try throwing ordering and deletion into the same form.
+
+ ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True)
+
+ initial = [
+ {'choice': u'Calexico', 'votes': 100},
+ {'choice': u'Fergie', 'votes': 900},
+ {'choice': u'The Decemberists', 'votes': 500},
+ ]
+ formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(form.as_ul())
+
+ self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
+<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
+<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
+<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
+<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
+<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
+<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
+<li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
+<li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
+<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
+<li>Choice: <input type="text" name="choices-3-choice" /></li>
+<li>Votes: <input type="text" name="choices-3-votes" /></li>
+<li>Order: <input type="text" name="choices-3-ORDER" /></li>
+<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>""")
+
+ # Let's delete Fergie, and put The Decemberists ahead of Calexico.
+
+ data = {
+ 'choices-TOTAL_FORMS': '4', # the number of forms rendered
+ 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ 'choices-0-ORDER': '1',
+ 'choices-0-DELETE': '',
+ 'choices-1-choice': 'Fergie',
+ 'choices-1-votes': '900',
+ 'choices-1-ORDER': '2',
+ 'choices-1-DELETE': 'on',
+ 'choices-2-choice': 'The Decemberists',
+ 'choices-2-votes': '500',
+ 'choices-2-ORDER': '0',
+ 'choices-2-DELETE': '',
+ 'choices-3-choice': '',
+ 'choices-3-votes': '',
+ 'choices-3-ORDER': '',
+ 'choices-3-DELETE': '',
+ }
+
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ form_output = []
+
+ for form in formset.ordered_forms:
+ form_output.append(form.cleaned_data)
+
+ self.assertEqual(form_output, [
+ {'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'},
+ {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'},
+ ])
+ self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}])
+
+ def test_invalid_deleted_form_with_ordering(self):
+ # Should be able to get ordered forms from a valid formset even if a
+ # deleted form would have been invalid.
+
+ class Person(Form):
+ name = CharField()
+
+ PeopleForm = formset_factory(form=Person, can_delete=True, can_order=True)
+
+ p = PeopleForm({
+ 'form-0-name': u'',
+ 'form-0-DELETE': u'on', # no name!
+ 'form-TOTAL_FORMS': 1,
+ 'form-INITIAL_FORMS': 1,
+ 'form-MAX_NUM_FORMS': 1
+ })
+
+ self.assertTrue(p.is_valid())
+ self.assertEqual(p.ordered_forms, [])
+
+ def test_clean_hook(self):
+ # FormSet clean hook ##########################################################
+ # FormSets have a hook for doing extra validation that shouldn't be tied to any
+ # particular form. It follows the same pattern as the clean hook on Forms.
+
+ # We start out with a some duplicate data.
+
+ data = {
+ 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
+ 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'drinks-MAX_NUM_FORMS': '0', # max number of forms
+ 'drinks-0-name': 'Gin and Tonic',
+ 'drinks-1-name': 'Gin and Tonic',
+ }
+
+ formset = FavoriteDrinksFormSet(data, prefix='drinks')
+ self.assertFalse(formset.is_valid())
+
+ # Any errors raised by formset.clean() are available via the
+ # formset.non_form_errors() method.
+
+ for error in formset.non_form_errors():
+ self.assertEqual(str(error), 'You may only specify a drink once.')
+
+ # Make sure we didn't break the valid case.
+
+ data = {
+ 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
+ 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'drinks-MAX_NUM_FORMS': '0', # max number of forms
+ 'drinks-0-name': 'Gin and Tonic',
+ 'drinks-1-name': 'Bloody Mary',
+ }
+
+ formset = FavoriteDrinksFormSet(data, prefix='drinks')
+ self.assertTrue(formset.is_valid())
+ self.assertEqual(formset.non_form_errors(), [])
+
+ def test_limiting_max_forms(self):
+ # Limiting the maximum number of forms ########################################
+ # Base case for max_num.
+
+ # When not passed, max_num will take its default value of None, i.e. unlimited
+ # number of forms, only controlled by the value of the extra parameter.
+
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3)
+ formset = LimitedFavoriteDrinkFormSet()
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
+<tr><th><label for="id_form-2-name">Name:</label></th><td><input type="text" name="form-2-name" id="id_form-2-name" /></td></tr>""")
+
+ # If max_num is 0 then no form is rendered at all.
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=0)
+ formset = LimitedFavoriteDrinkFormSet()
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), "")
+
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
+ formset = LimitedFavoriteDrinkFormSet()
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
+
+ # Ensure that max_num has no effect when extra is less than max_num.
+
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
+ formset = LimitedFavoriteDrinkFormSet()
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>""")
+
+ def test_max_num_with_initial_data(self):
+ # max_num with initial data
+
+ # When not passed, max_num will take its default value of None, i.e. unlimited
+ # number of forms, only controlled by the values of the initial and extra
+ # parameters.
+
+ initial = [
+ {'name': 'Fernet and Coke'},
+ ]
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1)
+ formset = LimitedFavoriteDrinkFormSet(initial=initial)
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Fernet and Coke" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
+
+ def test_max_num_zero(self):
+ # If max_num is 0 then no form is rendered at all, even if extra and initial
+ # are specified.
+
+ initial = [
+ {'name': 'Fernet and Coke'},
+ {'name': 'Bloody Mary'},
+ ]
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
+ formset = LimitedFavoriteDrinkFormSet(initial=initial)
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), "")
+
+ def test_more_initial_than_max_num(self):
+ # More initial forms than max_num will result in only the first max_num of
+ # them to be displayed with no extra forms.
+
+ initial = [
+ {'name': 'Gin Tonic'},
+ {'name': 'Bloody Mary'},
+ {'name': 'Jack and Coke'},
+ ]
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
+ formset = LimitedFavoriteDrinkFormSet(initial=initial)
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" value="Bloody Mary" id="id_form-1-name" /></td></tr>""")
+
+ # One form from initial and extra=3 with max_num=2 should result in the one
+ # initial form and one extra.
+ initial = [
+ {'name': 'Gin Tonic'},
+ ]
+ LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
+ formset = LimitedFavoriteDrinkFormSet(initial=initial)
+ form_output = []
+
+ for form in formset.forms:
+ form_output.append(str(form))
+
+ self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
+<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
+
+ def test_regression_6926(self):
+ # Regression test for #6926 ##################################################
+ # Make sure the management form has the correct prefix.
+
+ formset = FavoriteDrinksFormSet()
+ self.assertEqual(formset.management_form.prefix, 'form')
+
+ formset = FavoriteDrinksFormSet(data={})
+ self.assertEqual(formset.management_form.prefix, 'form')
+
+ formset = FavoriteDrinksFormSet(initial={})
+ self.assertEqual(formset.management_form.prefix, 'form')
+
+ def test_regression_12878(self):
+ # Regression test for #12878 #################################################
+
+ data = {
+ 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
+ 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
+ 'drinks-MAX_NUM_FORMS': '0', # max number of forms
+ 'drinks-0-name': 'Gin and Tonic',
+ 'drinks-1-name': 'Gin and Tonic',
+ }
+
+ formset = FavoriteDrinksFormSet(data, prefix='drinks')
+ self.assertFalse(formset.is_valid())
+ self.assertEqual(formset.non_form_errors(), [u'You may only specify a drink once.'])
diff --git a/parts/django/tests/regressiontests/forms/tests/input_formats.py b/parts/django/tests/regressiontests/forms/tests/input_formats.py
new file mode 100644
index 0000000..498c6de
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/input_formats.py
@@ -0,0 +1,894 @@
+from datetime import time, date, datetime
+from unittest import TestCase
+
+from django import forms
+from django.conf import settings
+from django.utils.translation import activate, deactivate
+
+
+class LocalizedTimeTests(TestCase):
+ def setUp(self):
+ self.old_TIME_INPUT_FORMATS = settings.TIME_INPUT_FORMATS
+ self.old_USE_L10N = settings.USE_L10N
+
+ settings.TIME_INPUT_FORMATS = ["%I:%M:%S %p", "%I:%M %p"]
+ settings.USE_L10N = True
+
+ activate('de')
+
+ def tearDown(self):
+ settings.TIME_INPUT_FORMATS = self.old_TIME_INPUT_FORMATS
+ settings.USE_L10N = self.old_USE_L10N
+
+ 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")
+
+
+class CustomTimeInputFormatsTests(TestCase):
+ def setUp(self):
+ self.old_TIME_INPUT_FORMATS = settings.TIME_INPUT_FORMATS
+ settings.TIME_INPUT_FORMATS = ["%I:%M:%S %p", "%I:%M %p"]
+
+ def tearDown(self):
+ settings.TIME_INPUT_FORMATS = self.old_TIME_INPUT_FORMATS
+
+ 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(TestCase):
+ 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")
+
+
+class LocalizedDateTests(TestCase):
+ def setUp(self):
+ self.old_DATE_INPUT_FORMATS = settings.DATE_INPUT_FORMATS
+ self.old_USE_L10N = settings.USE_L10N
+
+ settings.DATE_INPUT_FORMATS = ["%d/%m/%Y", "%d-%m-%Y"]
+ settings.USE_L10N = True
+
+ activate('de')
+
+ def tearDown(self):
+ settings.DATE_INPUT_FORMATS = self.old_DATE_INPUT_FORMATS
+ settings.USE_L10N = self.old_USE_L10N
+
+ 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')
+
+ # 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")
+
+class CustomDateInputFormatsTests(TestCase):
+ def setUp(self):
+ self.old_DATE_INPUT_FORMATS = settings.DATE_INPUT_FORMATS
+ settings.DATE_INPUT_FORMATS = ["%d.%m.%Y", "%d-%m-%Y"]
+
+ def tearDown(self):
+ settings.DATE_INPUT_FORMATS = self.old_DATE_INPUT_FORMATS
+
+ 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(TestCase):
+ 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")
+
+class LocalizedDateTimeTests(TestCase):
+ def setUp(self):
+ self.old_DATETIME_INPUT_FORMATS = settings.DATETIME_INPUT_FORMATS
+ self.old_USE_L10N = settings.USE_L10N
+
+ settings.DATETIME_INPUT_FORMATS = ["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"]
+ settings.USE_L10N = True
+
+ activate('de')
+
+ def tearDown(self):
+ settings.DATETIME_INPUT_FORMATS = self.old_DATETIME_INPUT_FORMATS
+ settings.USE_L10N = self.old_USE_L10N
+
+ 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')
+
+ # 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")
+
+
+class CustomDateTimeInputFormatsTests(TestCase):
+ def setUp(self):
+ self.old_DATETIME_INPUT_FORMATS = settings.DATETIME_INPUT_FORMATS
+ settings.DATETIME_INPUT_FORMATS = ["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"]
+
+ def tearDown(self):
+ settings.DATETIME_INPUT_FORMATS = self.old_DATETIME_INPUT_FORMATS
+
+ 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(TestCase):
+ 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/parts/django/tests/regressiontests/forms/tests/media.py b/parts/django/tests/regressiontests/forms/tests/media.py
new file mode 100644
index 0000000..eb59d13
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/media.py
@@ -0,0 +1,460 @@
+# -*- coding: utf-8 -*-
+from unittest import TestCase
+from django.conf import settings
+from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget
+
+
+class FormsMediaTestCase(TestCase):
+ # Tests for the media handling on widgets and forms
+ def setUp(self):
+ super(FormsMediaTestCase, self).setUp()
+ self.original_media_url = settings.MEDIA_URL
+ settings.MEDIA_URL = 'http://media.example.com/media/'
+
+ def tearDown(self):
+ settings.MEDIA_URL = self.original_media_url
+ super(FormsMediaTestCase, self).tearDown()
+
+ def test_construction(self):
+ # Check construction of media objects
+ m = Media(css={'all': ('path/to/css1','/path/to/css2')}, js=('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3'))
+ self.assertEqual(str(m), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>""")
+
+ class Foo:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ m3 = Media(Foo)
+ self.assertEqual(str(m3), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>""")
+
+ # A widget can exist without a media definition
+ class MyWidget(TextInput):
+ pass
+
+ w = MyWidget()
+ self.assertEqual(str(w.media), '')
+
+ def test_media_dsl(self):
+ ###############################################################
+ # DSL Class-based media definitions
+ ###############################################################
+
+ # A widget can define media if it needs to.
+ # Any absolute path will be preserved; relative paths are combined
+ # with the value of settings.MEDIA_URL
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ w1 = MyWidget1()
+ self.assertEqual(str(w1.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>""")
+
+ # Media objects can be interrogated by media type
+ self.assertEqual(str(w1.media['css']), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />""")
+
+ self.assertEqual(str(w1.media['js']), """<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>""")
+
+ def test_combine_media(self):
+ # Media objects can be combined. Any given media resource will appear only
+ # once. Duplicated media definitions are ignored.
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget2(TextInput):
+ class Media:
+ css = {
+ 'all': ('/path/to/css2','/path/to/css3')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ class MyWidget3(TextInput):
+ class Media:
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ w1 = MyWidget1()
+ w2 = MyWidget2()
+ w3 = MyWidget3()
+ self.assertEqual(str(w1.media + w2.media + w3.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ # Check that media addition hasn't affected the original objects
+ self.assertEqual(str(w1.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>""")
+
+ # Regression check for #12879: specifying the same CSS or JS file
+ # multiple times in a single Media instance should result in that file
+ # only being included once.
+ class MyWidget4(TextInput):
+ class Media:
+ css = {'all': ('/path/to/css1', '/path/to/css1')}
+ js = ('/path/to/js1', '/path/to/js1')
+
+ w4 = MyWidget4()
+ self.assertEqual(str(w4.media), """<link href="/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>""")
+
+ def test_media_property(self):
+ ###############################################################
+ # Property-based media definitions
+ ###############################################################
+
+ # Widget media can be defined as a property
+ class MyWidget4(TextInput):
+ def _media(self):
+ return Media(css={'all': ('/some/path',)}, js = ('/some/js',))
+ media = property(_media)
+
+ w4 = MyWidget4()
+ self.assertEqual(str(w4.media), """<link href="/some/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/some/js"></script>""")
+
+ # Media properties can reference the media of their parents
+ class MyWidget5(MyWidget4):
+ def _media(self):
+ return super(MyWidget5, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
+ media = property(_media)
+
+ w5 = MyWidget5()
+ self.assertEqual(str(w5.media), """<link href="/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/some/js"></script>
+<script type="text/javascript" src="/other/js"></script>""")
+
+ def test_media_property_parent_references(self):
+ # Media properties can reference the media of their parents,
+ # even if the parent media was defined using a class
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget6(MyWidget1):
+ def _media(self):
+ return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
+ media = property(_media)
+
+ w6 = MyWidget6()
+ self.assertEqual(str(w6.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/other/js"></script>""")
+
+ def test_media_inheritance(self):
+ ###############################################################
+ # Inheritance of media
+ ###############################################################
+
+ # If a widget extends another but provides no media definition, it inherits the parent widget's media
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget7(MyWidget1):
+ pass
+
+ w7 = MyWidget7()
+ self.assertEqual(str(w7.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>""")
+
+ # If a widget extends another but defines media, it extends the parent widget's media by default
+ class MyWidget8(MyWidget1):
+ class Media:
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ w8 = MyWidget8()
+ self.assertEqual(str(w8.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ def test_media_inheritance_from_property(self):
+ # If a widget extends another but defines media, it extends the parents widget's media,
+ # even if the parent defined media using a property.
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget4(TextInput):
+ def _media(self):
+ return Media(css={'all': ('/some/path',)}, js = ('/some/js',))
+ media = property(_media)
+
+ class MyWidget9(MyWidget4):
+ class Media:
+ css = {
+ 'all': ('/other/path',)
+ }
+ js = ('/other/js',)
+
+ w9 = MyWidget9()
+ self.assertEqual(str(w9.media), """<link href="/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/some/js"></script>
+<script type="text/javascript" src="/other/js"></script>""")
+
+ # A widget can disable media inheritance by specifying 'extend=False'
+ class MyWidget10(MyWidget1):
+ class Media:
+ extend = False
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ w10 = MyWidget10()
+ self.assertEqual(str(w10.media), """<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ def test_media_inheritance_extends(self):
+ # A widget can explicitly enable full media inheritance by specifying 'extend=True'
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget11(MyWidget1):
+ class Media:
+ extend = True
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ w11 = MyWidget11()
+ self.assertEqual(str(w11.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ def test_media_inheritance_single_type(self):
+ # A widget can enable inheritance of one media type by specifying extend as a tuple
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget12(MyWidget1):
+ class Media:
+ extend = ('css',)
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ w12 = MyWidget12()
+ self.assertEqual(str(w12.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ def test_multi_media(self):
+ ###############################################################
+ # Multi-media handling for CSS
+ ###############################################################
+
+ # A widget can define CSS media for multiple output media types
+ class MultimediaWidget(TextInput):
+ class Media:
+ css = {
+ 'screen, print': ('/file1','/file2'),
+ 'screen': ('/file3',),
+ 'print': ('/file4',)
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ multimedia = MultimediaWidget()
+ self.assertEqual(str(multimedia.media), """<link href="/file4" type="text/css" media="print" rel="stylesheet" />
+<link href="/file3" type="text/css" media="screen" rel="stylesheet" />
+<link href="/file1" type="text/css" media="screen, print" rel="stylesheet" />
+<link href="/file2" type="text/css" media="screen, print" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ def test_multi_widget(self):
+ ###############################################################
+ # Multiwidget media handling
+ ###############################################################
+
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget2(TextInput):
+ class Media:
+ css = {
+ 'all': ('/path/to/css2','/path/to/css3')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ class MyWidget3(TextInput):
+ class Media:
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ # MultiWidgets have a default media definition that gets all the
+ # media from the component widgets
+ class MyMultiWidget(MultiWidget):
+ def __init__(self, attrs=None):
+ widgets = [MyWidget1, MyWidget2, MyWidget3]
+ super(MyMultiWidget, self).__init__(widgets, attrs)
+
+ mymulti = MyMultiWidget()
+ self.assertEqual(str(mymulti.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ def test_form_media(self):
+ ###############################################################
+ # Media processing for forms
+ ###############################################################
+
+ class MyWidget1(TextInput):
+ class Media:
+ css = {
+ 'all': ('path/to/css1','/path/to/css2')
+ }
+ js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+ class MyWidget2(TextInput):
+ class Media:
+ css = {
+ 'all': ('/path/to/css2','/path/to/css3')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ class MyWidget3(TextInput):
+ class Media:
+ css = {
+ 'all': ('/path/to/css3','path/to/css1')
+ }
+ js = ('/path/to/js1','/path/to/js4')
+
+ # You can ask a form for the media required by its widgets.
+ class MyForm(Form):
+ field1 = CharField(max_length=20, widget=MyWidget1())
+ field2 = CharField(max_length=20, widget=MyWidget2())
+ f1 = MyForm()
+ self.assertEqual(str(f1.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ # Form media can be combined to produce a single media definition.
+ class AnotherForm(Form):
+ field3 = CharField(max_length=20, widget=MyWidget3())
+ f2 = AnotherForm()
+ self.assertEqual(str(f1.media + f2.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>""")
+
+ # Forms can also define media, following the same rules as widgets.
+ class FormWithMedia(Form):
+ field1 = CharField(max_length=20, widget=MyWidget1())
+ field2 = CharField(max_length=20, widget=MyWidget2())
+ class Media:
+ js = ('/some/form/javascript',)
+ css = {
+ 'all': ('/some/form/css',)
+ }
+ f3 = FormWithMedia()
+ self.assertEqual(str(f3.media), """<link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+<script type="text/javascript" src="/some/form/javascript"></script>""")
+
+ # Media works in templates
+ from django.template import Template, Context
+ self.assertEqual(Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})), """<script type="text/javascript" src="/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="/path/to/js4"></script>
+<script type="text/javascript" src="/some/form/javascript"></script><link href="http://media.example.com/media/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />""")
diff --git a/parts/django/tests/regressiontests/forms/tests/models.py b/parts/django/tests/regressiontests/forms/tests/models.py
new file mode 100644
index 0000000..af7473e
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/models.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+import datetime
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.conf import settings
+from django.db import connection
+from django.forms import Form, ModelForm, FileField, ModelChoiceField
+from django.test import TestCase
+from regressiontests.forms.models import ChoiceModel, ChoiceOptionModel, ChoiceFieldModel, FileModel, Group, BoundaryModel, Defaults
+
+
+class ChoiceFieldForm(ModelForm):
+ class Meta:
+ model = ChoiceFieldModel
+
+
+class FileForm(Form):
+ file1 = FileField()
+
+
+class TestTicket12510(TestCase):
+ ''' It is not necessary to generate choices for ModelChoiceField (regression test for #12510). '''
+ def setUp(self):
+ self.groups = [Group.objects.create(name=name) for name in 'abc']
+ self.old_debug = settings.DEBUG
+ # turn debug on to get access to connection.queries
+ settings.DEBUG = True
+
+ def tearDown(self):
+ settings.DEBUG = self.old_debug
+
+ def test_choices_not_fetched_when_not_rendering(self):
+ initial_queries = len(connection.queries)
+ field = ModelChoiceField(Group.objects.order_by('-name'))
+ self.assertEqual('a', field.clean(self.groups[0].pk).name)
+ # only one query is required to pull the model from DB
+ self.assertEqual(initial_queries+1, len(connection.queries))
+
+class ModelFormCallableModelDefault(TestCase):
+ def test_no_empty_option(self):
+ "If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)."
+ option = ChoiceOptionModel.objects.create(name='default')
+
+ choices = list(ChoiceFieldForm().fields['choice'].choices)
+ self.assertEquals(len(choices), 1)
+ self.assertEquals(choices[0], (option.pk, unicode(option)))
+
+ def test_callable_initial_value(self):
+ "The initial value for a callable default returning a queryset is the pk (refs #13769)"
+ obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
+ obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
+ obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
+ self.assertEquals(ChoiceFieldForm().as_p(), """<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice" value="1" id="initial-id_choice" /></p>
+<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" id="id_choice_int">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice_int" value="1" id="initial-id_choice_int" /></p>
+<p><label for="id_multi_choice">Multi choice:</label> <select multiple="multiple" name="multi_choice" id="id_multi_choice">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice" value="1" id="initial-id_multi_choice_0" /> Hold down "Control", or "Command" on a Mac, to select more than one.</p>
+<p><label for="id_multi_choice_int">Multi choice int:</label> <select multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice_int" value="1" id="initial-id_multi_choice_int_0" /> Hold down "Control", or "Command" on a Mac, to select more than one.</p>""")
+
+ def test_initial_instance_value(self):
+ "Initial instances for model fields may also be instances (refs #7287)"
+ obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
+ obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
+ obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
+ self.assertEquals(ChoiceFieldForm(initial={
+ 'choice': obj2,
+ 'choice_int': obj2,
+ 'multi_choice': [obj2,obj3],
+ 'multi_choice_int': ChoiceOptionModel.objects.exclude(name="default"),
+ }).as_p(), """<p><label for="id_choice">Choice:</label> <select name="choice" id="id_choice">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice" value="2" id="initial-id_choice" /></p>
+<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" id="id_choice_int">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice_int" value="2" id="initial-id_choice_int" /></p>
+<p><label for="id_multi_choice">Multi choice:</label> <select multiple="multiple" name="multi_choice" id="id_multi_choice">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3" selected="selected">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice" value="2" id="initial-id_multi_choice_0" />
+<input type="hidden" name="initial-multi_choice" value="3" id="initial-id_multi_choice_1" /> Hold down "Control", or "Command" on a Mac, to select more than one.</p>
+<p><label for="id_multi_choice_int">Multi choice int:</label> <select multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3" selected="selected">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice_int" value="2" id="initial-id_multi_choice_int_0" />
+<input type="hidden" name="initial-multi_choice_int" value="3" id="initial-id_multi_choice_int_1" /> Hold down "Control", or "Command" on a Mac, to select more than one.</p>""")
+
+
+
+class FormsModelTestCase(TestCase):
+ def test_unicode_filename(self):
+ # FileModel with unicode filename and data #########################
+ f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False)
+ self.assertTrue(f.is_valid())
+ self.assertTrue('file1' in f.cleaned_data)
+ m = FileModel.objects.create(file=f.cleaned_data['file1'])
+ self.assertEqual(m.file.name, u'tests/\u6211\u96bb\u6c23\u588a\u8239\u88dd\u6eff\u6652\u9c54.txt')
+ m.delete()
+
+ def test_boundary_conditions(self):
+ # Boundary conditions on a PostitiveIntegerField #########################
+ class BoundaryForm(ModelForm):
+ class Meta:
+ model = BoundaryModel
+
+ f = BoundaryForm({'positive_integer': 100})
+ self.assertTrue(f.is_valid())
+ f = BoundaryForm({'positive_integer': 0})
+ self.assertTrue(f.is_valid())
+ f = BoundaryForm({'positive_integer': -100})
+ self.assertFalse(f.is_valid())
+
+ def test_formfield_initial(self):
+ # Formfield initial values ########
+ # If the model has default values for some fields, they are used as the formfield
+ # initial values.
+ class DefaultsForm(ModelForm):
+ class Meta:
+ model = Defaults
+
+ self.assertEqual(DefaultsForm().fields['name'].initial, u'class default value')
+ self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))
+ self.assertEqual(DefaultsForm().fields['value'].initial, 42)
+ r1 = DefaultsForm()['callable_default'].as_widget()
+ r2 = DefaultsForm()['callable_default'].as_widget()
+ self.assertNotEqual(r1, r2)
+
+ # In a ModelForm that is passed an instance, the initial values come from the
+ # instance's values, not the model's defaults.
+ foo_instance = Defaults(name=u'instance value', def_date=datetime.date(1969, 4, 4), value=12)
+ instance_form = DefaultsForm(instance=foo_instance)
+ self.assertEqual(instance_form.initial['name'], u'instance value')
+ self.assertEqual(instance_form.initial['def_date'], datetime.date(1969, 4, 4))
+ self.assertEqual(instance_form.initial['value'], 12)
+
+ from django.forms import CharField
+
+ class ExcludingForm(ModelForm):
+ name = CharField(max_length=255)
+
+ class Meta:
+ model = Defaults
+ exclude = ['name', 'callable_default']
+
+ f = ExcludingForm({'name': u'Hello', 'value': 99, 'def_date': datetime.date(1999, 3, 2)})
+ self.assertTrue(f.is_valid())
+ self.assertEqual(f.cleaned_data['name'], u'Hello')
+ obj = f.save()
+ self.assertEqual(obj.name, u'class default value')
+ self.assertEqual(obj.value, 99)
+ self.assertEqual(obj.def_date, datetime.date(1999, 3, 2))
diff --git a/parts/django/tests/regressiontests/forms/tests/regressions.py b/parts/django/tests/regressiontests/forms/tests/regressions.py
new file mode 100644
index 0000000..ddec740
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/regressions.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+from unittest import TestCase
+from django.forms import *
+from django.utils.translation import ugettext_lazy, activate, deactivate
+
+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.assertEqual(TestForm(auto_id=False).as_p(), u'<p>F1: <input type="text" class="special" name="f1" maxlength="10" /></p>\n<p>F2: <input type="text" class="special" name="f2" /></p>')
+
+ def test_regression_3600(self):
+ # Tests for form i18n #
+ # There were some problems with form translations in #3600
+
+ class SomeForm(Form):
+ username = CharField(max_length=10, label=ugettext_lazy('Username'))
+
+ f = SomeForm()
+ self.assertEqual(f.as_p(), '<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>')
+
+ # Translations are done at rendering time, so multi-lingual apps can define forms)
+ activate('de')
+ self.assertEqual(f.as_p(), '<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>')
+ activate('pl')
+ self.assertEqual(f.as_p(), u'<p><label for="id_username">Nazwa u\u017cytkownika:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>')
+ deactivate()
+
+ def test_regression_5216(self):
+ # There was some problems with form translations in #5216
+ class SomeForm(Form):
+ field_1 = CharField(max_length=10, label=ugettext_lazy('field_1'))
+ field_2 = CharField(max_length=10, label=ugettext_lazy('field_2'), widget=TextInput(attrs={'id': 'field_2_id'}))
+
+ f = SomeForm()
+ self.assertEqual(f['field_1'].label_tag(), '<label for="id_field_1">field_1</label>')
+ self.assertEqual(f['field_2'].label_tag(), '<label for="field_2_id">field_2</label>')
+
+ # Unicode decoding problems...
+ GENDERS = ((u'\xc5', u'En tied\xe4'), (u'\xf8', u'Mies'), (u'\xdf', u'Nainen'))
+
+ class SomeForm(Form):
+ somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf')
+
+ f = SomeForm()
+ self.assertEqual(f.as_p(), u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>')
+
+ # Testing choice validation with UTF-8 bytestrings as input (these are the
+ # Russian abbreviations "мес." and "шт.".
+ UNITS = (('\xd0\xbc\xd0\xb5\xd1\x81.', '\xd0\xbc\xd0\xb5\xd1\x81.'), ('\xd1\x88\xd1\x82.', '\xd1\x88\xd1\x82.'))
+ f = ChoiceField(choices=UNITS)
+ self.assertEqual(f.clean(u'\u0448\u0442.'), u'\u0448\u0442.')
+ self.assertEqual(f.clean('\xd1\x88\xd1\x82.'), u'\u0448\u0442.')
+
+ # Translated error messages used to be buggy.
+ activate('ru')
+ f = SomeForm({})
+ self.assertEqual(f.as_p(), u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>')
+ deactivate()
+
+ # Deep copying translated text shouldn't raise an error)
+ from django.utils.translation import gettext_lazy
+
+ class CopyForm(Form):
+ degree = IntegerField(widget=Select(choices=((1, gettext_lazy('test')),)))
+
+ f = CopyForm()
+
+ def test_misc(self):
+ # There once was a problem with Form fields called "data". Let's make sure that
+ # doesn't come back.
+ class DataForm(Form):
+ data = CharField(max_length=10)
+
+ f = DataForm({'data': 'xyzzy'})
+ self.assertTrue(f.is_valid())
+ self.assertEqual(f.cleaned_data, {'data': u'xyzzy'})
+
+ # A form with *only* hidden fields that has errors is going to be very unusual.
+ class HiddenForm(Form):
+ data = IntegerField(widget=HiddenInput)
+
+ f = HiddenForm({})
+ self.assertEqual(f.as_p(), u'<ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul>\n<p> <input type="hidden" name="data" id="id_data" /></p>')
+ self.assertEqual(f.as_table(), u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>')
+
+ def test_xss_error_messages(self):
+ ###################################################
+ # Tests for XSS vulnerabilities in error messages #
+ ###################################################
+
+ # The forms layer doesn't escape input values directly because error messages
+ # might be presented in non-HTML contexts. Instead, the message is just marked
+ # for escaping by the template engine. So we'll need to construct a little
+ # silly template to trigger the escaping.
+ from django.template import Template, Context
+ t = Template('{{ form.errors }}')
+
+ class SomeForm(Form):
+ field = ChoiceField(choices=[('one', 'One')])
+
+ f = SomeForm({'field': '<script>'})
+ self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. &lt;script&gt; is not one of the available choices.</li></ul></li></ul>')
+
+ class SomeForm(Form):
+ field = MultipleChoiceField(choices=[('one', 'One')])
+
+ f = SomeForm({'field': ['<script>']})
+ self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. &lt;script&gt; is not one of the available choices.</li></ul></li></ul>')
+
+ from regressiontests.forms.models import ChoiceModel
+
+ class SomeForm(Form):
+ field = ModelMultipleChoiceField(ChoiceModel.objects.all())
+
+ f = SomeForm({'field': ['<script>']})
+ self.assertEqual(t.render(Context({'form': f})), u'<ul class="errorlist"><li>field<ul class="errorlist"><li>&quot;&lt;script&gt;&quot; is not a valid value for a primary key.</li></ul></li></ul>')
+
+ def test_regression_14234(self):
+ """
+ Re-cleaning an instance that was added via a ModelForm should not raise
+ a pk uniqueness error.
+
+ """
+ class CheeseForm(ModelForm):
+ class Meta:
+ model = Cheese
+
+ form = CheeseForm({
+ 'name': 'Brie',
+ })
+
+ self.assertTrue(form.is_valid())
+
+ obj = form.save()
+ obj.name = 'Camembert'
+ obj.full_clean()
diff --git a/parts/django/tests/regressiontests/forms/tests/util.py b/parts/django/tests/regressiontests/forms/tests/util.py
new file mode 100644
index 0000000..8ea3bdd
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/util.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+from unittest import TestCase
+from django.core.exceptions import ValidationError
+from django.forms.util import *
+from django.utils.translation import ugettext_lazy
+
+
+class FormsUtilTestCase(TestCase):
+ # Tests for forms/util.py module.
+
+ def test_flatatt(self):
+ ###########
+ # flatatt #
+ ###########
+
+ self.assertEqual(flatatt({'id': "header"}), u' id="header"')
+ self.assertEqual(flatatt({'class': "news", 'title': "Read this"}), u' class="news" title="Read this"')
+ self.assertEqual(flatatt({}), u'')
+
+ def test_validation_error(self):
+ ###################
+ # ValidationError #
+ ###################
+
+ # Can take a string.
+ self.assertEqual(str(ErrorList(ValidationError("There was an error.").messages)),
+ '<ul class="errorlist"><li>There was an error.</li></ul>')
+
+ # Can take a unicode string.
+ self.assertEqual(str(ErrorList(ValidationError(u"Not \u03C0.").messages)),
+ '<ul class="errorlist"><li>Not π.</li></ul>')
+
+ # Can take a lazy string.
+ self.assertEqual(str(ErrorList(ValidationError(ugettext_lazy("Error.")).messages)),
+ '<ul class="errorlist"><li>Error.</li></ul>')
+
+ # Can take a list.
+ self.assertEqual(str(ErrorList(ValidationError(["Error one.", "Error two."]).messages)),
+ '<ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>')
+
+ # Can take a mixture in a list.
+ self.assertEqual(str(ErrorList(ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages)),
+ '<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>')
+
+ class VeryBadError:
+ def __unicode__(self): return u"A very bad error."
+
+ # Can take a non-string.
+ self.assertEqual(str(ErrorList(ValidationError(VeryBadError()).messages)),
+ '<ul class="errorlist"><li>A very bad error.</li></ul>')
+
+ # Escapes non-safe input but not input marked safe.
+ example = 'Example of link: <a href="http://www.example.com/">example</a>'
+ self.assertEqual(str(ErrorList([example])),
+ '<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>')
+ self.assertEqual(str(ErrorList([mark_safe(example)])),
+ '<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>')
diff --git a/parts/django/tests/regressiontests/forms/tests/validators.py b/parts/django/tests/regressiontests/forms/tests/validators.py
new file mode 100644
index 0000000..ed8e8fb
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/validators.py
@@ -0,0 +1,17 @@
+from unittest import TestCase
+
+from django import forms
+from django.core import validators
+from django.core.exceptions import ValidationError
+
+
+class TestFieldWithValidators(TestCase):
+ def test_all_errors_get_reported(self):
+ field = forms.CharField(
+ validators=[validators.validate_integer, validators.validate_email]
+ )
+ self.assertRaises(ValidationError, field.clean, 'not int nor mail')
+ try:
+ field.clean('not int nor mail')
+ except ValidationError, e:
+ self.assertEqual(2, len(e.messages))
diff --git a/parts/django/tests/regressiontests/forms/tests/widgets.py b/parts/django/tests/regressiontests/forms/tests/widgets.py
new file mode 100644
index 0000000..9e7c6f6
--- /dev/null
+++ b/parts/django/tests/regressiontests/forms/tests/widgets.py
@@ -0,0 +1,1070 @@
+# -*- coding: utf-8 -*-
+import datetime
+from decimal import Decimal
+import re
+import time
+from unittest import TestCase
+from django.conf import settings
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.forms import *
+from django.forms.widgets import RadioFieldRenderer
+from django.utils import copycompat as copy
+from django.utils import formats
+from django.utils.safestring import mark_safe
+from django.utils.translation import activate, deactivate
+
+
+
+class FormsWidgetTestCase(TestCase):
+ # Each Widget class corresponds to an HTML form widget. A Widget knows how to
+ # render itself, given a field name and some data. Widgets don't perform
+ # validation.
+ def test_textinput(self):
+ w = TextInput()
+ self.assertEqual(w.render('email', ''), u'<input type="text" name="email" />')
+ self.assertEqual(w.render('email', None), u'<input type="text" name="email" />')
+ self.assertEqual(w.render('email', 'test@example.com'), u'<input type="text" name="email" value="test@example.com" />')
+ self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="text" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />')
+ self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="text" name="email" value="test@example.com" class="fun" />')
+
+ # Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii
+ # characters in output, so we're displaying the repr() here.
+ self.assertEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), u'<input type="text" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" class="fun" />')
+
+ # You can also pass 'attrs' to the constructor:
+ w = TextInput(attrs={'class': 'fun'})
+ self.assertEqual(w.render('email', ''), u'<input type="text" class="fun" name="email" />')
+ self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="text" class="fun" value="foo@example.com" name="email" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = TextInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="text" class="special" name="email" />')
+
+ # 'attrs' can be safe-strings if needed)
+ w = TextInput(attrs={'onBlur': mark_safe("function('foo')")})
+ self.assertEqual(w.render('email', ''), u'<input onBlur="function(\'foo\')" type="text" name="email" />')
+
+ def test_passwordinput(self):
+ w = PasswordInput()
+ self.assertEqual(w.render('email', ''), u'<input type="password" name="email" />')
+ self.assertEqual(w.render('email', None), u'<input type="password" name="email" />')
+ self.assertEqual(w.render('email', 'test@example.com'), u'<input type="password" name="email" value="test@example.com" />')
+ self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="password" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />')
+ self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="password" name="email" value="test@example.com" class="fun" />')
+
+ # You can also pass 'attrs' to the constructor:
+ w = PasswordInput(attrs={'class': 'fun'})
+ self.assertEqual(w.render('email', ''), u'<input type="password" class="fun" name="email" />')
+ self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="password" class="fun" value="foo@example.com" name="email" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = PasswordInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="password" class="special" name="email" />')
+
+ self.assertEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />')
+
+ # The render_value argument lets you specify whether the widget should render
+ # its value. You may want to do this for security reasons.
+ w = PasswordInput(render_value=True)
+ self.assertEqual(w.render('email', 'secret'), u'<input type="password" name="email" value="secret" />')
+
+ w = PasswordInput(render_value=False)
+ self.assertEqual(w.render('email', ''), u'<input type="password" name="email" />')
+ self.assertEqual(w.render('email', None), u'<input type="password" name="email" />')
+ self.assertEqual(w.render('email', 'secret'), u'<input type="password" name="email" />')
+
+ w = PasswordInput(attrs={'class': 'fun'}, render_value=False)
+ self.assertEqual(w.render('email', 'secret'), u'<input type="password" class="fun" name="email" />')
+
+ def test_hiddeninput(self):
+ w = HiddenInput()
+ self.assertEqual(w.render('email', ''), u'<input type="hidden" name="email" />')
+ self.assertEqual(w.render('email', None), u'<input type="hidden" name="email" />')
+ self.assertEqual(w.render('email', 'test@example.com'), u'<input type="hidden" name="email" value="test@example.com" />')
+ self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="hidden" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />')
+ self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="hidden" name="email" value="test@example.com" class="fun" />')
+
+ # You can also pass 'attrs' to the constructor:
+ w = HiddenInput(attrs={'class': 'fun'})
+ self.assertEqual(w.render('email', ''), u'<input type="hidden" class="fun" name="email" />')
+ self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="hidden" class="fun" value="foo@example.com" name="email" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = HiddenInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="hidden" class="special" name="email" />')
+
+ self.assertEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = HiddenInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('email', '', attrs={'class': 'special'}), u'<input type="hidden" class="special" name="email" />')
+
+ # Boolean values are rendered to their string forms ("True" and "False").
+ w = HiddenInput()
+ self.assertEqual(w.render('get_spam', False), u'<input type="hidden" name="get_spam" value="False" />')
+ self.assertEqual(w.render('get_spam', True), u'<input type="hidden" name="get_spam" value="True" />')
+
+ def test_multiplehiddeninput(self):
+ w = MultipleHiddenInput()
+ self.assertEqual(w.render('email', []), u'')
+ self.assertEqual(w.render('email', None), u'')
+ self.assertEqual(w.render('email', ['test@example.com']), u'<input type="hidden" name="email" value="test@example.com" />')
+ self.assertEqual(w.render('email', ['some "quoted" & ampersanded value']), u'<input type="hidden" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />')
+ self.assertEqual(w.render('email', ['test@example.com', 'foo@example.com']), u'<input type="hidden" name="email" value="test@example.com" />\n<input type="hidden" name="email" value="foo@example.com" />')
+ self.assertEqual(w.render('email', ['test@example.com'], attrs={'class': 'fun'}), u'<input type="hidden" name="email" value="test@example.com" class="fun" />')
+ self.assertEqual(w.render('email', ['test@example.com', 'foo@example.com'], attrs={'class': 'fun'}), u'<input type="hidden" name="email" value="test@example.com" class="fun" />\n<input type="hidden" name="email" value="foo@example.com" class="fun" />')
+
+ # You can also pass 'attrs' to the constructor:
+ w = MultipleHiddenInput(attrs={'class': 'fun'})
+ self.assertEqual(w.render('email', []), u'')
+ self.assertEqual(w.render('email', ['foo@example.com']), u'<input type="hidden" class="fun" value="foo@example.com" name="email" />')
+ self.assertEqual(w.render('email', ['foo@example.com', 'test@example.com']), u'<input type="hidden" class="fun" value="foo@example.com" name="email" />\n<input type="hidden" class="fun" value="test@example.com" name="email" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = MultipleHiddenInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('email', ['foo@example.com'], attrs={'class': 'special'}), u'<input type="hidden" class="special" value="foo@example.com" name="email" />')
+
+ self.assertEqual(w.render('email', ['ŠĐĆŽćžšđ'], attrs={'class': 'fun'}), u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = MultipleHiddenInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('email', ['foo@example.com'], attrs={'class': 'special'}), u'<input type="hidden" class="special" value="foo@example.com" name="email" />')
+
+ # Each input gets a separate ID.
+ w = MultipleHiddenInput()
+ self.assertEqual(w.render('letters', list('abc'), attrs={'id': 'hideme'}), u'<input type="hidden" name="letters" value="a" id="hideme_0" />\n<input type="hidden" name="letters" value="b" id="hideme_1" />\n<input type="hidden" name="letters" value="c" id="hideme_2" />')
+
+ def test_fileinput(self):
+ # FileInput widgets don't ever show the value, because the old value is of no use
+ # if you are updating the form or if the provided file generated an error.
+ w = FileInput()
+ self.assertEqual(w.render('email', ''), u'<input type="file" name="email" />')
+ self.assertEqual(w.render('email', None), u'<input type="file" name="email" />')
+ self.assertEqual(w.render('email', 'test@example.com'), u'<input type="file" name="email" />')
+ self.assertEqual(w.render('email', 'some "quoted" & ampersanded value'), u'<input type="file" name="email" />')
+ self.assertEqual(w.render('email', 'test@example.com', attrs={'class': 'fun'}), u'<input type="file" name="email" class="fun" />')
+
+ # You can also pass 'attrs' to the constructor:
+ w = FileInput(attrs={'class': 'fun'})
+ self.assertEqual(w.render('email', ''), u'<input type="file" class="fun" name="email" />')
+ self.assertEqual(w.render('email', 'foo@example.com'), u'<input type="file" class="fun" name="email" />')
+
+ self.assertEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), u'<input type="file" class="fun" name="email" />')
+
+ # Test for the behavior of _has_changed for FileInput. The value of data will
+ # more than likely come from request.FILES. The value of initial data will
+ # likely be a filename stored in the database. Since its value is of no use to
+ # a FileInput it is ignored.
+ w = FileInput()
+
+ # No file was uploaded and no initial data.
+ self.assertFalse(w._has_changed(u'', None))
+
+ # A file was uploaded and no initial data.
+ self.assertTrue(w._has_changed(u'', {'filename': 'resume.txt', 'content': 'My resume'}))
+
+ # A file was not uploaded, but there is initial data
+ self.assertFalse(w._has_changed(u'resume.txt', None))
+
+ # A file was uploaded and there is initial data (file identity is not dealt
+ # with here)
+ self.assertTrue(w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
+
+ def test_textarea(self):
+ w = Textarea()
+ self.assertEqual(w.render('msg', ''), u'<textarea rows="10" cols="40" name="msg"></textarea>')
+ self.assertEqual(w.render('msg', None), u'<textarea rows="10" cols="40" name="msg"></textarea>')
+ self.assertEqual(w.render('msg', 'value'), u'<textarea rows="10" cols="40" name="msg">value</textarea>')
+ self.assertEqual(w.render('msg', 'some "quoted" & ampersanded value'), u'<textarea rows="10" cols="40" name="msg">some &quot;quoted&quot; &amp; ampersanded value</textarea>')
+ self.assertEqual(w.render('msg', mark_safe('pre &quot;quoted&quot; value')), u'<textarea rows="10" cols="40" name="msg">pre &quot;quoted&quot; value</textarea>')
+ self.assertEqual(w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20}), u'<textarea class="pretty" rows="20" cols="40" name="msg">value</textarea>')
+
+ # You can also pass 'attrs' to the constructor:
+ w = Textarea(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('msg', ''), u'<textarea rows="10" cols="40" name="msg" class="pretty"></textarea>')
+ self.assertEqual(w.render('msg', 'example'), u'<textarea rows="10" cols="40" name="msg" class="pretty">example</textarea>')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = Textarea(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('msg', '', attrs={'class': 'special'}), u'<textarea rows="10" cols="40" name="msg" class="special"></textarea>')
+
+ self.assertEqual(w.render('msg', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), u'<textarea rows="10" cols="40" name="msg" class="fun">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>')
+
+ def test_checkboxinput(self):
+ w = CheckboxInput()
+ self.assertEqual(w.render('is_cool', ''), u'<input type="checkbox" name="is_cool" />')
+ self.assertEqual(w.render('is_cool', None), u'<input type="checkbox" name="is_cool" />')
+ self.assertEqual(w.render('is_cool', False), u'<input type="checkbox" name="is_cool" />')
+ self.assertEqual(w.render('is_cool', True), u'<input checked="checked" type="checkbox" name="is_cool" />')
+
+ # Using any value that's not in ('', None, False, True) will check the checkbox
+ # and set the 'value' attribute.
+ self.assertEqual(w.render('is_cool', 'foo'), u'<input checked="checked" type="checkbox" name="is_cool" value="foo" />')
+
+ self.assertEqual(w.render('is_cool', False, attrs={'class': 'pretty'}), u'<input type="checkbox" name="is_cool" class="pretty" />')
+
+ # You can also pass 'attrs' to the constructor:
+ w = CheckboxInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('is_cool', ''), u'<input type="checkbox" class="pretty" name="is_cool" />')
+
+ # 'attrs' passed to render() get precedence over those passed to the constructor:
+ w = CheckboxInput(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('is_cool', '', attrs={'class': 'special'}), u'<input type="checkbox" class="special" name="is_cool" />')
+
+ # You can pass 'check_test' to the constructor. This is a callable that takes the
+ # value and returns True if the box should be checked.
+ w = CheckboxInput(check_test=lambda value: value.startswith('hello'))
+ self.assertEqual(w.render('greeting', ''), u'<input type="checkbox" name="greeting" />')
+ self.assertEqual(w.render('greeting', 'hello'), u'<input checked="checked" type="checkbox" name="greeting" value="hello" />')
+ self.assertEqual(w.render('greeting', 'hello there'), u'<input checked="checked" type="checkbox" name="greeting" value="hello there" />')
+ self.assertEqual(w.render('greeting', 'hello & goodbye'), u'<input checked="checked" type="checkbox" name="greeting" value="hello &amp; goodbye" />')
+
+ # A subtlety: If the 'check_test' argument cannot handle a value and raises any
+ # exception during its __call__, then the exception will be swallowed and the box
+ # will not be checked. In this example, the 'check_test' assumes the value has a
+ # startswith() method, which fails for the values True, False and None.
+ self.assertEqual(w.render('greeting', True), u'<input type="checkbox" name="greeting" />')
+ self.assertEqual(w.render('greeting', False), u'<input type="checkbox" name="greeting" />')
+ self.assertEqual(w.render('greeting', None), u'<input type="checkbox" name="greeting" />')
+
+ # The CheckboxInput widget will return False if the key is not found in the data
+ # dictionary (because HTML form submission doesn't send any result for unchecked
+ # checkboxes).
+ self.assertFalse(w.value_from_datadict({}, {}, 'testing'))
+
+ self.assertFalse(w._has_changed(None, None))
+ self.assertFalse(w._has_changed(None, u''))
+ self.assertFalse(w._has_changed(u'', None))
+ self.assertFalse(w._has_changed(u'', u''))
+ self.assertTrue(w._has_changed(False, u'on'))
+ self.assertFalse(w._has_changed(True, u'on'))
+ self.assertTrue(w._has_changed(True, u''))
+
+ def test_select(self):
+ w = Select()
+ self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
+<option value="J" selected="selected">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+
+ # If the value is None, none of the options are selected:
+ self.assertEqual(w.render('beatle', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
+<option value="J">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+
+ # If the value corresponds to a label (but not to an option value), none of the options are selected:
+ self.assertEqual(w.render('beatle', 'John', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
+<option value="J">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+
+ # The value is compared to its str():
+ self.assertEqual(w.render('num', 2, choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<select name="num">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+ self.assertEqual(w.render('num', '2', choices=[(1, 1), (2, 2), (3, 3)]), """<select name="num">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+ self.assertEqual(w.render('num', 2, choices=[(1, 1), (2, 2), (3, 3)]), """<select name="num">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+
+ # The 'choices' argument can be any iterable:
+ from itertools import chain
+ def get_choices():
+ for i in range(5):
+ yield (i, i)
+ self.assertEqual(w.render('num', 2, choices=get_choices()), """<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>""")
+ things = ({'id': 1, 'name': 'And Boom'}, {'id': 2, 'name': 'One More Thing!'})
+ class SomeForm(Form):
+ somechoice = ChoiceField(choices=chain((('', '-'*9),), [(thing['id'], thing['name']) for thing in things]))
+ f = SomeForm()
+ self.assertEqual(f.as_table(), u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>')
+ self.assertEqual(f.as_table(), u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>')
+ f = SomeForm({'somechoice': 2})
+ self.assertEqual(f.as_table(), u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="">---------</option>\n<option value="1">And Boom</option>\n<option value="2" selected="selected">One More Thing!</option>\n</select></td></tr>')
+
+ # You can also pass 'choices' to the constructor:
+ w = Select(choices=[(1, 1), (2, 2), (3, 3)])
+ self.assertEqual(w.render('num', 2), """<select name="num">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+
+ # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
+ self.assertEqual(w.render('num', 2, choices=[(4, 4), (5, 5)]), """<select name="num">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+</select>""")
+
+ # Choices are escaped correctly
+ self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me')))), """<select name="escape">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="bad">you &amp; me</option>
+<option value="good">you &gt; me</option>
+</select>""")
+
+ # Unicode choices are correctly rendered as HTML
+ self.assertEqual(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>')
+
+ # If choices is passed to the constructor and is a generator, it can be iterated
+ # over multiple times without getting consumed:
+ w = Select(choices=get_choices())
+ self.assertEqual(w.render('num', 2), """<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>""")
+ self.assertEqual(w.render('num', 3), """<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3" selected="selected">3</option>
+<option value="4">4</option>
+</select>""")
+
+ # Choices can be nested one level in order to create HTML optgroups:
+ w.choices=(('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
+ self.assertEqual(w.render('nestchoice', None), """<select name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>""")
+
+ self.assertEqual(w.render('nestchoice', 'outer1'), """<select name="nestchoice">
+<option value="outer1" selected="selected">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>""")
+
+ self.assertEqual(w.render('nestchoice', 'inner1'), """<select name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1" selected="selected">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>""")
+
+ def test_nullbooleanselect(self):
+ w = NullBooleanSelect()
+ self.assertTrue(w.render('is_cool', True), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>""")
+ self.assertEqual(w.render('is_cool', False), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>""")
+ self.assertEqual(w.render('is_cool', None), """<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>""")
+ self.assertEqual(w.render('is_cool', '2'), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>""")
+ self.assertEqual(w.render('is_cool', '3'), """<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>""")
+ self.assertTrue(w._has_changed(False, None))
+ self.assertTrue(w._has_changed(None, False))
+ self.assertFalse(w._has_changed(None, None))
+ self.assertFalse(w._has_changed(False, False))
+ self.assertTrue(w._has_changed(True, False))
+ self.assertTrue(w._has_changed(True, None))
+ self.assertTrue(w._has_changed(True, False))
+
+ def test_selectmultiple(self):
+ w = SelectMultiple()
+ self.assertEqual(w.render('beatles', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
+<option value="J" selected="selected">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+ self.assertEqual(w.render('beatles', ['J', 'P'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
+<option value="J" selected="selected">John</option>
+<option value="P" selected="selected">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+ self.assertEqual(w.render('beatles', ['J', 'P', 'R'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
+<option value="J" selected="selected">John</option>
+<option value="P" selected="selected">Paul</option>
+<option value="G">George</option>
+<option value="R" selected="selected">Ringo</option>
+</select>""")
+
+ # If the value is None, none of the options are selected:
+ self.assertEqual(w.render('beatles', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
+<option value="J">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+
+ # If the value corresponds to a label (but not to an option value), none of the options are selected:
+ self.assertEqual(w.render('beatles', ['John'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
+<option value="J">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>""")
+
+ # If multiple values are given, but some of them are not valid, the valid ones are selected:
+ self.assertEqual(w.render('beatles', ['J', 'G', 'foo'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select multiple="multiple" name="beatles">
+<option value="J" selected="selected">John</option>
+<option value="P">Paul</option>
+<option value="G" selected="selected">George</option>
+<option value="R">Ringo</option>
+</select>""")
+
+ # The value is compared to its str():
+ self.assertEqual(w.render('nums', [2], choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<select multiple="multiple" name="nums">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+ self.assertEqual(w.render('nums', ['2'], choices=[(1, 1), (2, 2), (3, 3)]), """<select multiple="multiple" name="nums">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+ self.assertEqual(w.render('nums', [2], choices=[(1, 1), (2, 2), (3, 3)]), """<select multiple="multiple" name="nums">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+
+ # The 'choices' argument can be any iterable:
+ def get_choices():
+ for i in range(5):
+ yield (i, i)
+ self.assertEqual(w.render('nums', [2], choices=get_choices()), """<select multiple="multiple" name="nums">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>""")
+
+ # You can also pass 'choices' to the constructor:
+ w = SelectMultiple(choices=[(1, 1), (2, 2), (3, 3)])
+ self.assertEqual(w.render('nums', [2]), """<select multiple="multiple" name="nums">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+</select>""")
+
+ # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
+ self.assertEqual(w.render('nums', [2], choices=[(4, 4), (5, 5)]), """<select multiple="multiple" name="nums">
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+</select>""")
+
+ # Choices are escaped correctly
+ self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me')))), """<select multiple="multiple" name="escape">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="bad">you &amp; me</option>
+<option value="good">you &gt; me</option>
+</select>""")
+
+ # Unicode choices are correctly rendered as HTML
+ self.assertEqual(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), u'<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>')
+
+ # Test the usage of _has_changed
+ self.assertFalse(w._has_changed(None, None))
+ self.assertFalse(w._has_changed([], None))
+ self.assertTrue(w._has_changed(None, [u'1']))
+ self.assertFalse(w._has_changed([1, 2], [u'1', u'2']))
+ self.assertTrue(w._has_changed([1, 2], [u'1']))
+ self.assertTrue(w._has_changed([1, 2], [u'1', u'3']))
+
+ # Choices can be nested one level in order to create HTML optgroups:
+ w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
+ self.assertEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>""")
+
+ self.assertEqual(w.render('nestchoice', ['outer1']), """<select multiple="multiple" name="nestchoice">
+<option value="outer1" selected="selected">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>""")
+
+ self.assertEqual(w.render('nestchoice', ['inner1']), """<select multiple="multiple" name="nestchoice">
+<option value="outer1">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1" selected="selected">Inner 1</option>
+<option value="inner2">Inner 2</option>
+</optgroup>
+</select>""")
+
+ self.assertEqual(w.render('nestchoice', ['outer1', 'inner2']), """<select multiple="multiple" name="nestchoice">
+<option value="outer1" selected="selected">Outer 1</option>
+<optgroup label="Group &quot;1&quot;">
+<option value="inner1">Inner 1</option>
+<option value="inner2" selected="selected">Inner 2</option>
+</optgroup>
+</select>""")
+
+ def test_radioselect(self):
+ w = RadioSelect()
+ self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input checked="checked" type="radio" name="beatle" value="J" /> John</label></li>
+<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
+<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
+<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # If the value is None, none of the options are checked:
+ self.assertEqual(w.render('beatle', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input type="radio" name="beatle" value="J" /> John</label></li>
+<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
+<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
+<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # If the value corresponds to a label (but not to an option value), none of the options are checked:
+ self.assertEqual(w.render('beatle', 'John', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input type="radio" name="beatle" value="J" /> John</label></li>
+<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
+<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
+<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # The value is compared to its str():
+ self.assertEqual(w.render('num', 2, choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>""")
+ self.assertEqual(w.render('num', '2', choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>""")
+ self.assertEqual(w.render('num', 2, choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>""")
+
+ # The 'choices' argument can be any iterable:
+ def get_choices():
+ for i in range(5):
+ yield (i, i)
+ self.assertEqual(w.render('num', 2, choices=get_choices()), """<ul>
+<li><label><input type="radio" name="num" value="0" /> 0</label></li>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+<li><label><input type="radio" name="num" value="4" /> 4</label></li>
+</ul>""")
+
+ # You can also pass 'choices' to the constructor:
+ w = RadioSelect(choices=[(1, 1), (2, 2), (3, 3)])
+ self.assertEqual(w.render('num', 2), """<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>""")
+
+ # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
+ self.assertEqual(w.render('num', 2, choices=[(4, 4), (5, 5)]), """<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+<li><label><input type="radio" name="num" value="4" /> 4</label></li>
+<li><label><input type="radio" name="num" value="5" /> 5</label></li>
+</ul>""")
+
+ # RadioSelect uses a RadioFieldRenderer to render the individual radio inputs.
+ # You can manipulate that object directly to customize the way the RadioSelect
+ # is rendered.
+ w = RadioSelect()
+ r = w.get_renderer('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+ inp_set1 = []
+ inp_set2 = []
+ inp_set3 = []
+ inp_set4 = []
+
+ for inp in r:
+ inp_set1.append(str(inp))
+ inp_set2.append('%s<br />' % inp)
+ inp_set3.append('<p>%s %s</p>' % (inp.tag(), inp.choice_label))
+ inp_set4.append('%s %s %s %s %s' % (inp.name, inp.value, inp.choice_value, inp.choice_label, inp.is_checked()))
+
+ self.assertEqual('\n'.join(inp_set1), """<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>
+<label><input type="radio" name="beatle" value="P" /> Paul</label>
+<label><input type="radio" name="beatle" value="G" /> George</label>
+<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
+ self.assertEqual('\n'.join(inp_set2), """<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label><br />
+<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
+<label><input type="radio" name="beatle" value="G" /> George</label><br />
+<label><input type="radio" name="beatle" value="R" /> Ringo</label><br />""")
+ self.assertEqual('\n'.join(inp_set3), """<p><input checked="checked" type="radio" name="beatle" value="J" /> John</p>
+<p><input type="radio" name="beatle" value="P" /> Paul</p>
+<p><input type="radio" name="beatle" value="G" /> George</p>
+<p><input type="radio" name="beatle" value="R" /> Ringo</p>""")
+ self.assertEqual('\n'.join(inp_set4), """beatle J J John True
+beatle J P Paul False
+beatle J G George False
+beatle J R Ringo False""")
+
+ # You can create your own custom renderers for RadioSelect to use.
+ class MyRenderer(RadioFieldRenderer):
+ def render(self):
+ return u'<br />\n'.join([unicode(choice) for choice in self])
+ w = RadioSelect(renderer=MyRenderer)
+ self.assertEqual(w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<label><input type="radio" name="beatle" value="J" /> John</label><br />
+<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
+<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
+<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
+
+ # Or you can use custom RadioSelect fields that use your custom renderer.
+ class CustomRadioSelect(RadioSelect):
+ renderer = MyRenderer
+ w = CustomRadioSelect()
+ self.assertEqual(w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<label><input type="radio" name="beatle" value="J" /> John</label><br />
+<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
+<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
+<label><input type="radio" name="beatle" value="R" /> Ringo</label>""")
+
+ # A RadioFieldRenderer object also allows index access to individual RadioInput
+ w = RadioSelect()
+ r = w.get_renderer('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+ self.assertEqual(str(r[1]), '<label><input type="radio" name="beatle" value="P" /> Paul</label>')
+ self.assertEqual(str(r[0]), '<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>')
+ self.assertTrue(r[0].is_checked())
+ self.assertFalse(r[1].is_checked())
+ self.assertEqual((r[1].name, r[1].value, r[1].choice_value, r[1].choice_label), ('beatle', u'J', u'P', u'Paul'))
+
+ try:
+ r[10]
+ self.fail("This offset should not exist.")
+ except IndexError:
+ pass
+
+ # Choices are escaped correctly
+ w = RadioSelect()
+ self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me')))), """<ul>
+<li><label><input type="radio" name="escape" value="bad" /> you &amp; me</label></li>
+<li><label><input type="radio" name="escape" value="good" /> you &gt; me</label></li>
+</ul>""")
+
+ # Unicode choices are correctly rendered as HTML
+ w = RadioSelect()
+ self.assertEqual(unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])), u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="radio" name="email" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>')
+
+ # Attributes provided at instantiation are passed to the constituent inputs
+ w = RadioSelect(attrs={'id':'foo'})
+ self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label for="foo_0"><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li>
+<li><label for="foo_1"><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li>
+<li><label for="foo_2"><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li>
+<li><label for="foo_3"><input type="radio" id="foo_3" value="R" name="beatle" /> Ringo</label></li>
+</ul>""")
+
+ # Attributes provided at render-time are passed to the constituent inputs
+ w = RadioSelect()
+ self.assertEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}), """<ul>
+<li><label for="bar_0"><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li>
+<li><label for="bar_1"><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li>
+<li><label for="bar_2"><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li>
+<li><label for="bar_3"><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></li>
+</ul>""")
+
+ def test_checkboxselectmultiple(self):
+ w = CheckboxSelectMultiple()
+ self.assertEqual(w.render('beatles', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>""")
+ self.assertEqual(w.render('beatles', ['J', 'P'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>""")
+ self.assertEqual(w.render('beatles', ['J', 'P', 'R'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # If the value is None, none of the options are selected:
+ self.assertEqual(w.render('beatles', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # If the value corresponds to a label (but not to an option value), none of the options are selected:
+ self.assertEqual(w.render('beatles', ['John'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # If multiple values are given, but some of them are not valid, the valid ones are selected:
+ self.assertEqual(w.render('beatles', ['J', 'G', 'foo'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>""")
+
+ # The value is compared to its str():
+ self.assertEqual(w.render('nums', [2], choices=[('1', '1'), ('2', '2'), ('3', '3')]), """<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>""")
+ self.assertEqual(w.render('nums', ['2'], choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>""")
+ self.assertEqual(w.render('nums', [2], choices=[(1, 1), (2, 2), (3, 3)]), """<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>""")
+
+ # The 'choices' argument can be any iterable:
+ def get_choices():
+ for i in range(5):
+ yield (i, i)
+ self.assertEqual(w.render('nums', [2], choices=get_choices()), """<ul>
+<li><label><input type="checkbox" name="nums" value="0" /> 0</label></li>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="nums" value="4" /> 4</label></li>
+</ul>""")
+
+ # You can also pass 'choices' to the constructor:
+ w = CheckboxSelectMultiple(choices=[(1, 1), (2, 2), (3, 3)])
+ self.assertEqual(w.render('nums', [2]), """<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>""")
+
+ # If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
+ self.assertEqual(w.render('nums', [2], choices=[(4, 4), (5, 5)]), """<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="nums" value="4" /> 4</label></li>
+<li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
+</ul>""")
+
+ # Choices are escaped correctly
+ self.assertEqual(w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me')))), """<ul>
+<li><label><input type="checkbox" name="escape" value="1" /> 1</label></li>
+<li><label><input type="checkbox" name="escape" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="escape" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="escape" value="bad" /> you &amp; me</label></li>
+<li><label><input type="checkbox" name="escape" value="good" /> you &gt; me</label></li>
+</ul>""")
+
+ # Test the usage of _has_changed
+ self.assertFalse(w._has_changed(None, None))
+ self.assertFalse(w._has_changed([], None))
+ self.assertTrue(w._has_changed(None, [u'1']))
+ self.assertFalse(w._has_changed([1, 2], [u'1', u'2']))
+ self.assertTrue(w._has_changed([1, 2], [u'1']))
+ self.assertTrue(w._has_changed([1, 2], [u'1', u'3']))
+ self.assertFalse(w._has_changed([2, 1], [u'1', u'2']))
+
+ # Unicode choices are correctly rendered as HTML
+ self.assertEqual(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>')
+
+ # Each input gets a separate ID
+ self.assertEqual(CheckboxSelectMultiple().render('letters', list('ac'), choices=zip(list('abc'), list('ABC')), attrs={'id': 'abc'}), """<ul>
+<li><label for="abc_0"><input checked="checked" type="checkbox" name="letters" value="a" id="abc_0" /> A</label></li>
+<li><label for="abc_1"><input type="checkbox" name="letters" value="b" id="abc_1" /> B</label></li>
+<li><label for="abc_2"><input checked="checked" type="checkbox" name="letters" value="c" id="abc_2" /> C</label></li>
+</ul>""")
+
+ def test_multi(self):
+ class MyMultiWidget(MultiWidget):
+ def decompress(self, value):
+ if value:
+ return value.split('__')
+ return ['', '']
+ def format_output(self, rendered_widgets):
+ return u'<br />'.join(rendered_widgets)
+
+ w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})))
+ self.assertEqual(w.render('name', ['john', 'lennon']), u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />')
+ self.assertEqual(w.render('name', 'john__lennon'), u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />')
+ self.assertEqual(w.render('name', 'john__lennon', attrs={'id':'foo'}), u'<input id="foo_0" type="text" class="big" value="john" name="name_0" /><br /><input id="foo_1" type="text" class="small" value="lennon" name="name_1" />')
+ w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
+ self.assertEqual(w.render('name', ['john', 'lennon']), u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />')
+
+ w = MyMultiWidget(widgets=(TextInput(), TextInput()))
+
+ # test with no initial data
+ self.assertTrue(w._has_changed(None, [u'john', u'lennon']))
+
+ # test when the data is the same as initial
+ self.assertFalse(w._has_changed(u'john__lennon', [u'john', u'lennon']))
+
+ # test when the first widget's data has changed
+ self.assertTrue(w._has_changed(u'john__lennon', [u'alfred', u'lennon']))
+
+ # test when the last widget's data has changed. this ensures that it is not
+ # short circuiting while testing the widgets.
+ self.assertTrue(w._has_changed(u'john__lennon', [u'john', u'denver']))
+
+ def test_splitdatetime(self):
+ w = SplitDateTimeWidget()
+ self.assertEqual(w.render('date', ''), u'<input type="text" name="date_0" /><input type="text" name="date_1" />')
+ self.assertEqual(w.render('date', None), u'<input type="text" name="date_0" /><input type="text" name="date_1" />')
+ self.assertEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />')
+ self.assertEqual(w.render('date', [datetime.date(2006, 1, 10), datetime.time(7, 30)]), u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />')
+
+ # You can also pass 'attrs' to the constructor. In this case, the attrs will be
+ w = SplitDateTimeWidget(attrs={'class': 'pretty'})
+ self.assertEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />')
+
+ # Use 'date_format' and 'time_format' to change the way a value is displayed.
+ w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
+ self.assertEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), u'<input type="text" name="date_0" value="10/01/2006" /><input type="text" name="date_1" value="07:30" />')
+
+ self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'2008-05-06', u'12:40:00']))
+ self.assertFalse(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06/05/2008', u'12:40']))
+ self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06/05/2008', u'12:41']))
+
+ def test_datetimeinput(self):
+ w = DateTimeInput()
+ self.assertEqual(w.render('date', None), u'<input type="text" name="date" />')
+ d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
+ self.assertEqual(str(d), '2007-09-17 12:51:34.482548')
+
+ # The microseconds are trimmed on display, by default.
+ self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="2007-09-17 12:51:34" />')
+ self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34)), u'<input type="text" name="date" value="2007-09-17 12:51:34" />')
+ self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), u'<input type="text" name="date" value="2007-09-17 12:51:00" />')
+
+ # Use 'format' to change the way a value is displayed.
+ w = DateTimeInput(format='%d/%m/%Y %H:%M')
+ self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17/09/2007 12:51" />')
+ self.assertFalse(w._has_changed(d, '17/09/2007 12:51'))
+
+ # Make sure a custom format works with _has_changed. The hidden input will use
+ data = datetime.datetime(2010, 3, 6, 12, 0, 0)
+ custom_format = '%d.%m.%Y %H:%M'
+ w = DateTimeInput(format=custom_format)
+ self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
+
+ def test_dateinput(self):
+ w = DateInput()
+ self.assertEqual(w.render('date', None), u'<input type="text" name="date" />')
+ d = datetime.date(2007, 9, 17)
+ self.assertEqual(str(d), '2007-09-17')
+
+ self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="2007-09-17" />')
+ self.assertEqual(w.render('date', datetime.date(2007, 9, 17)), u'<input type="text" name="date" value="2007-09-17" />')
+
+ # We should be able to initialize from a unicode value.
+ self.assertEqual(w.render('date', u'2007-09-17'), u'<input type="text" name="date" value="2007-09-17" />')
+
+ # Use 'format' to change the way a value is displayed.
+ w = DateInput(format='%d/%m/%Y')
+ self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17/09/2007" />')
+ self.assertFalse(w._has_changed(d, '17/09/2007'))
+
+ # Make sure a custom format works with _has_changed. The hidden input will use
+ data = datetime.date(2010, 3, 6)
+ custom_format = '%d.%m.%Y'
+ w = DateInput(format=custom_format)
+ self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
+
+ def test_timeinput(self):
+ w = TimeInput()
+ self.assertEqual(w.render('time', None), u'<input type="text" name="time" />')
+ t = datetime.time(12, 51, 34, 482548)
+ self.assertEqual(str(t), '12:51:34.482548')
+
+ # The microseconds are trimmed on display, by default.
+ self.assertEqual(w.render('time', t), u'<input type="text" name="time" value="12:51:34" />')
+ self.assertEqual(w.render('time', datetime.time(12, 51, 34)), u'<input type="text" name="time" value="12:51:34" />')
+ self.assertEqual(w.render('time', datetime.time(12, 51)), u'<input type="text" name="time" value="12:51:00" />')
+
+ # We should be able to initialize from a unicode value.
+ self.assertEqual(w.render('time', u'13:12:11'), u'<input type="text" name="time" value="13:12:11" />')
+
+ # Use 'format' to change the way a value is displayed.
+ w = TimeInput(format='%H:%M')
+ self.assertEqual(w.render('time', t), u'<input type="text" name="time" value="12:51" />')
+ self.assertFalse(w._has_changed(t, '12:51'))
+
+ # Make sure a custom format works with _has_changed. The hidden input will use
+ data = datetime.time(13, 0)
+ custom_format = '%I:%M %p'
+ w = TimeInput(format=custom_format)
+ self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
+
+ def test_splithiddendatetime(self):
+ from django.forms.widgets import SplitHiddenDateTimeWidget
+
+ w = SplitHiddenDateTimeWidget()
+ self.assertEqual(w.render('date', ''), u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />')
+ d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
+ self.assertEqual(str(d), '2007-09-17 12:51:34.482548')
+ self.assertEqual(w.render('date', d), u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />')
+ self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34)), u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />')
+ self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:00" />')
+
+
+class FormsI18NWidgetsTestCase(TestCase):
+ def setUp(self):
+ super(FormsI18NWidgetsTestCase, self).setUp()
+ self.old_use_l10n = getattr(settings, 'USE_L10N', False)
+ settings.USE_L10N = True
+ activate('de-at')
+
+ def tearDown(self):
+ deactivate()
+ settings.USE_L10N = self.old_use_l10n
+ super(FormsI18NWidgetsTestCase, self).tearDown()
+
+ def test_splitdatetime(self):
+ w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
+ self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06.05.2008', u'12:41']))
+
+ def test_datetimeinput(self):
+ w = DateTimeInput()
+ d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
+ w.is_localized = True
+ self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17.09.2007 12:51:34" />')
+
+ def test_dateinput(self):
+ w = DateInput()
+ d = datetime.date(2007, 9, 17)
+ w.is_localized = True
+ self.assertEqual(w.render('date', d), u'<input type="text" name="date" value="17.09.2007" />')
+
+ def test_timeinput(self):
+ w = TimeInput()
+ t = datetime.time(12, 51, 34, 482548)
+ w.is_localized = True
+ self.assertEqual(w.render('time', t), u'<input type="text" name="time" value="12:51:34" />')
+
+ def test_splithiddendatetime(self):
+ from django.forms.widgets import SplitHiddenDateTimeWidget
+
+ w = SplitHiddenDateTimeWidget()
+ w.is_localized = True
+ self.assertEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), u'<input type="hidden" name="date_0" value="17.09.2007" /><input type="hidden" name="date_1" value="12:51:00" />')
+
+
+class SelectAndTextWidget(MultiWidget):
+ """
+ MultiWidget subclass
+ """
+ def __init__(self, choices=[]):
+ widgets = [
+ RadioSelect(choices=choices),
+ TextInput
+ ]
+ super(SelectAndTextWidget, self).__init__(widgets)
+
+ def _set_choices(self, choices):
+ """
+ When choices are set for this widget, we want to pass those along to the Select widget
+ """
+ self.widgets[0].choices = choices
+ def _get_choices(self):
+ """
+ The choices for this widget are the Select widget's choices
+ """
+ return self.widgets[0].choices
+ choices = property(_get_choices, _set_choices)
+
+
+class WidgetTests(TestCase):
+ def test_12048(self):
+ # See ticket #12048.
+ w1 = SelectAndTextWidget(choices=[1,2,3])
+ w2 = copy.deepcopy(w1)
+ w2.choices = [4,5,6]
+ # w2 ought to be independent of w1, since MultiWidget ought
+ # to make a copy of its sub-widgets when it is copied.
+ self.assertEqual(w1.choices, [1,2,3])
+
+ def test_13390(self):
+ # See ticket #13390
+ class SplitDateForm(Form):
+ field = DateTimeField(widget=SplitDateTimeWidget, required=False)
+
+ form = SplitDateForm({'field': ''})
+ self.assertTrue(form.is_valid())
+ form = SplitDateForm({'field': ['', '']})
+ self.assertTrue(form.is_valid())
+
+ class SplitDateRequiredForm(Form):
+ field = DateTimeField(widget=SplitDateTimeWidget, required=True)
+
+ form = SplitDateRequiredForm({'field': ''})
+ self.assertFalse(form.is_valid())
+ form = SplitDateRequiredForm({'field': ['', '']})
+ self.assertFalse(form.is_valid())
+
diff --git a/parts/django/tests/regressiontests/formwizard/__init__.py b/parts/django/tests/regressiontests/formwizard/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/formwizard/__init__.py
diff --git a/parts/django/tests/regressiontests/formwizard/forms.py b/parts/django/tests/regressiontests/formwizard/forms.py
new file mode 100644
index 0000000..f458eda
--- /dev/null
+++ b/parts/django/tests/regressiontests/formwizard/forms.py
@@ -0,0 +1,18 @@
+from django import forms
+from django.contrib.formtools.wizard import FormWizard
+from django.http import HttpResponse
+
+class Page1(forms.Form):
+ name = forms.CharField(max_length=100)
+ thirsty = forms.NullBooleanField()
+
+class Page2(forms.Form):
+ address1 = forms.CharField(max_length=100)
+ address2 = forms.CharField(max_length=100)
+
+class Page3(forms.Form):
+ random_crap = forms.CharField(max_length=100)
+
+class ContactWizard(FormWizard):
+ def done(self, request, form_list):
+ return HttpResponse("")
diff --git a/parts/django/tests/regressiontests/formwizard/models.py b/parts/django/tests/regressiontests/formwizard/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/formwizard/models.py
diff --git a/parts/django/tests/regressiontests/formwizard/templates/forms/wizard.html b/parts/django/tests/regressiontests/formwizard/templates/forms/wizard.html
new file mode 100644
index 0000000..a31378f
--- /dev/null
+++ b/parts/django/tests/regressiontests/formwizard/templates/forms/wizard.html
@@ -0,0 +1,13 @@
+<html>
+ <body>
+ <p>Step {{ step }} of {{ step_count }}</p>
+ <form action="." method="post">
+ <table>
+ {{ form }}
+ </table>
+ <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
+ {{ previous_fields|safe }}
+ <input type="submit">
+ </form>
+ </body>
+</html> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/formwizard/tests.py b/parts/django/tests/regressiontests/formwizard/tests.py
new file mode 100644
index 0000000..0c94d2e
--- /dev/null
+++ b/parts/django/tests/regressiontests/formwizard/tests.py
@@ -0,0 +1,59 @@
+import re
+from django import forms
+from django.test import TestCase
+
+class FormWizardWithNullBooleanField(TestCase):
+ urls = 'regressiontests.formwizard.urls'
+
+ input_re = re.compile('name="([^"]+)" value="([^"]+)"')
+
+ wizard_url = '/wiz/'
+ wizard_step_data = (
+ {
+ '0-name': 'Pony',
+ '0-thirsty': '2',
+ },
+ {
+ '1-address1': '123 Main St',
+ '1-address2': 'Djangoland',
+ },
+ {
+ '2-random_crap': 'blah blah',
+ }
+ )
+
+ def grabFieldData(self, response):
+ """
+ Pull the appropriate field data from the context to pass to the next wizard step
+ """
+ previous_fields = response.context['previous_fields']
+ fields = {'wizard_step': response.context['step0']}
+
+ def grab(m):
+ fields[m.group(1)] = m.group(2)
+ return ''
+
+ self.input_re.sub(grab, previous_fields)
+ return fields
+
+ def checkWizardStep(self, response, step_no):
+ """
+ Helper function to test each step of the wizard
+ - Make sure the call succeeded
+ - Make sure response is the proper step number
+ - return the result from the post for the next step
+ """
+ step_count = len(self.wizard_step_data)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Step %d of %d' % (step_no, step_count))
+
+ data = self.grabFieldData(response)
+ data.update(self.wizard_step_data[step_no - 1])
+
+ return self.client.post(self.wizard_url, data)
+
+ def testWizard(self):
+ response = self.client.get(self.wizard_url)
+ for step_no in range(1, len(self.wizard_step_data) + 1):
+ response = self.checkWizardStep(response, step_no)
diff --git a/parts/django/tests/regressiontests/formwizard/urls.py b/parts/django/tests/regressiontests/formwizard/urls.py
new file mode 100644
index 0000000..d964bc6
--- /dev/null
+++ b/parts/django/tests/regressiontests/formwizard/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+from forms import ContactWizard, Page1, Page2, Page3
+
+urlpatterns = patterns('',
+ url(r'^wiz/$', ContactWizard([Page1, Page2, Page3])),
+ )
diff --git a/parts/django/tests/regressiontests/generic_inline_admin/__init__.py b/parts/django/tests/regressiontests/generic_inline_admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_inline_admin/__init__.py
diff --git a/parts/django/tests/regressiontests/generic_inline_admin/fixtures/users.xml b/parts/django/tests/regressiontests/generic_inline_admin/fixtures/users.xml
new file mode 100644
index 0000000..6cf441f
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_inline_admin/fixtures/users.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+</django-objects> \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/generic_inline_admin/models.py b/parts/django/tests/regressiontests/generic_inline_admin/models.py
new file mode 100644
index 0000000..329c487
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_inline_admin/models.py
@@ -0,0 +1,108 @@
+from django.db import models
+from django.contrib import admin
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+
+class Episode(models.Model):
+ name = models.CharField(max_length=100)
+
+class Media(models.Model):
+ """
+ Media that can associated to any object.
+ """
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey()
+ url = models.URLField(verify_exists=False)
+
+ def __unicode__(self):
+ return self.url
+
+class MediaInline(generic.GenericTabularInline):
+ model = Media
+
+class EpisodeAdmin(admin.ModelAdmin):
+ inlines = [
+ MediaInline,
+ ]
+admin.site.register(Episode, EpisodeAdmin)
+
+#
+# These models let us test the different GenericInline settings at
+# different urls in the admin site.
+#
+
+#
+# Generic inline with extra = 0
+#
+
+class EpisodeExtra(Episode):
+ pass
+
+class MediaExtraInline(generic.GenericTabularInline):
+ model = Media
+ extra = 0
+
+admin.site.register(EpisodeExtra, inlines=[MediaExtraInline])
+
+#
+# Generic inline with extra and max_num
+#
+
+class EpisodeMaxNum(Episode):
+ pass
+
+class MediaMaxNumInline(generic.GenericTabularInline):
+ model = Media
+ extra = 5
+ max_num = 2
+
+admin.site.register(EpisodeMaxNum, inlines=[MediaMaxNumInline])
+
+#
+# Generic inline with exclude
+#
+
+class EpisodeExclude(Episode):
+ pass
+
+class MediaExcludeInline(generic.GenericTabularInline):
+ model = Media
+ exclude = ['url']
+
+admin.site.register(EpisodeExclude, inlines=[MediaExcludeInline])
+
+#
+# Generic inline with unique_together
+#
+
+class PhoneNumber(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ phone_number = models.CharField(max_length=30)
+
+ class Meta:
+ unique_together = (('content_type', 'object_id', 'phone_number',),)
+
+class Contact(models.Model):
+ name = models.CharField(max_length=50)
+ phone_numbers = generic.GenericRelation(PhoneNumber)
+
+class PhoneNumberInline(generic.GenericTabularInline):
+ model = PhoneNumber
+
+admin.site.register(Contact, inlines=[PhoneNumberInline])
+
+#
+# Generic inline with can_delete=False
+#
+
+class EpisodePermanent(Episode):
+ pass
+
+class MediaPermanentInline(generic.GenericTabularInline):
+ model = Media
+ can_delete = False
+
+admin.site.register(EpisodePermanent, inlines=[MediaPermanentInline])
diff --git a/parts/django/tests/regressiontests/generic_inline_admin/tests.py b/parts/django/tests/regressiontests/generic_inline_admin/tests.py
new file mode 100644
index 0000000..d5531f0
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_inline_admin/tests.py
@@ -0,0 +1,214 @@
+# coding: utf-8
+
+from django.conf import settings
+from django.contrib.contenttypes.generic import generic_inlineformset_factory
+from django.test import TestCase
+
+# local test models
+from models import Episode, EpisodeExtra, EpisodeMaxNum, EpisodeExclude, \
+ Media, EpisodePermanent, MediaPermanentInline
+
+
+class GenericAdminViewTest(TestCase):
+ fixtures = ['users.xml']
+
+ def setUp(self):
+ # set TEMPLATE_DEBUG to True to ensure {% include %} will raise
+ # exceptions since that is how inlines are rendered and #9498 will
+ # bubble up if it is an issue.
+ self.original_template_debug = settings.TEMPLATE_DEBUG
+ settings.TEMPLATE_DEBUG = True
+ self.client.login(username='super', password='secret')
+
+ # Can't load content via a fixture (since the GenericForeignKey
+ # relies on content type IDs, which will vary depending on what
+ # other tests have been run), thus we do it here.
+ e = Episode.objects.create(name='This Week in Django')
+ self.episode_pk = e.pk
+ m = Media(content_object=e, url='http://example.com/podcast.mp3')
+ m.save()
+ self.mp3_media_pk = m.pk
+
+ m = Media(content_object=e, url='http://example.com/logo.png')
+ m.save()
+ self.png_media_pk = m.pk
+
+ def tearDown(self):
+ self.client.logout()
+ settings.TEMPLATE_DEBUG = self.original_template_debug
+
+ def testBasicAddGet(self):
+ """
+ A smoke test to ensure GET on the add_view works.
+ """
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/add/')
+ self.assertEqual(response.status_code, 200)
+
+ def testBasicEditGet(self):
+ """
+ A smoke test to ensure GET on the change_view works.
+ """
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk)
+ self.assertEqual(response.status_code, 200)
+
+ def testBasicAddPost(self):
+ """
+ A smoke test to ensure POST on add_view works.
+ """
+ post_data = {
+ "name": u"This Week in Django",
+ # inline data
+ "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"1",
+ "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"0",
+ "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": u"0",
+ }
+ response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/add/', post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+ def testBasicEditPost(self):
+ """
+ A smoke test to ensure POST on edit_view works.
+ """
+ post_data = {
+ "name": u"This Week in Django",
+ # inline data
+ "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"3",
+ "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"2",
+ "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": u"0",
+ "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.mp3_media_pk,
+ "generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3",
+ "generic_inline_admin-media-content_type-object_id-1-id": u"%d" % self.png_media_pk,
+ "generic_inline_admin-media-content_type-object_id-1-url": u"http://example.com/logo.png",
+ "generic_inline_admin-media-content_type-object_id-2-id": u"",
+ "generic_inline_admin-media-content_type-object_id-2-url": u"",
+ }
+ url = '/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk
+ response = self.client.post(url, post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+ def testGenericInlineFormset(self):
+ EpisodeMediaFormSet = generic_inlineformset_factory(Media, can_delete=False, extra=3)
+ e = Episode.objects.get(name='This Week in Django')
+
+ # Works with no queryset
+ formset = EpisodeMediaFormSet(instance=e)
+ self.assertEquals(len(formset.forms), 5)
+ self.assertEquals(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.mp3_media_pk)
+ self.assertEquals(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.png_media_pk)
+ self.assertEquals(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="text" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>')
+
+ # A queryset can be used to alter display ordering
+ formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.order_by('url'))
+ self.assertEquals(len(formset.forms), 5)
+ self.assertEquals(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk)
+ self.assertEquals(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.mp3_media_pk)
+ self.assertEquals(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="text" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>')
+
+
+ # Works with a queryset that omits items
+ formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.filter(url__endswith=".png"))
+ self.assertEquals(len(formset.forms), 4)
+ self.assertEquals(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk)
+ self.assertEquals(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>')
+
+ def testGenericInlineFormsetFactory(self):
+ # Regression test for #10522.
+ inline_formset = generic_inlineformset_factory(Media,
+ exclude=('url',))
+
+ # Regression test for #12340.
+ e = Episode.objects.get(name='This Week in Django')
+ formset = inline_formset(instance=e)
+ self.assertTrue(formset.get_queryset().ordered)
+
+class GenericInlineAdminParametersTest(TestCase):
+ fixtures = ['users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def _create_object(self, model):
+ """
+ Create a model with an attached Media object via GFK. We can't
+ load content via a fixture (since the GenericForeignKey relies on
+ content type IDs, which will vary depending on what other tests
+ have been run), thus we do it here.
+ """
+ e = model.objects.create(name='This Week in Django')
+ Media.objects.create(content_object=e, url='http://example.com/podcast.mp3')
+ return e
+
+ def testNoParam(self):
+ """
+ With one initial form, extra (default) at 3, there should be 4 forms.
+ """
+ e = self._create_object(Episode)
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/%s/' % e.pk)
+ formset = response.context['inline_admin_formsets'][0].formset
+ self.assertEqual(formset.total_form_count(), 4)
+ self.assertEqual(formset.initial_form_count(), 1)
+
+ def testExtraParam(self):
+ """
+ With extra=0, there should be one form.
+ """
+ e = self._create_object(EpisodeExtra)
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeextra/%s/' % e.pk)
+ formset = response.context['inline_admin_formsets'][0].formset
+ self.assertEqual(formset.total_form_count(), 1)
+ self.assertEqual(formset.initial_form_count(), 1)
+
+ def testMaxNumParam(self):
+ """
+ With extra=5 and max_num=2, there should be only 2 forms.
+ """
+ e = self._create_object(EpisodeMaxNum)
+ inline_form_data = '<input type="hidden" name="generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" value="2" id="id_generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" value="1" id="id_generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" />'
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodemaxnum/%s/' % e.pk)
+ formset = response.context['inline_admin_formsets'][0].formset
+ self.assertEqual(formset.total_form_count(), 2)
+ self.assertEqual(formset.initial_form_count(), 1)
+
+ def testExcludeParam(self):
+ """
+ Generic inline formsets should respect include.
+ """
+ e = self._create_object(EpisodeExclude)
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeexclude/%s/' % e.pk)
+ formset = response.context['inline_admin_formsets'][0].formset
+ self.assertFalse('url' in formset.forms[0], 'The formset has excluded "url" field.')
+
+class GenericInlineAdminWithUniqueTogetherTest(TestCase):
+ fixtures = ['users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testAdd(self):
+ post_data = {
+ "name": u"John Doe",
+ # inline data
+ "generic_inline_admin-phonenumber-content_type-object_id-TOTAL_FORMS": u"1",
+ "generic_inline_admin-phonenumber-content_type-object_id-INITIAL_FORMS": u"0",
+ "generic_inline_admin-phonenumber-content_type-object_id-MAX_NUM_FORMS": u"0",
+ "generic_inline_admin-phonenumber-content_type-object_id-0-id": "",
+ "generic_inline_admin-phonenumber-content_type-object_id-0-phone_number": "555-555-5555",
+ }
+ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/contact/add/')
+ self.assertEqual(response.status_code, 200)
+ response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/contact/add/', post_data)
+ self.assertEqual(response.status_code, 302) # redirect somewhere
+
+class NoInlineDeletionTest(TestCase):
+ def test_no_deletion(self):
+ fake_site = object()
+ inline = MediaPermanentInline(EpisodePermanent, fake_site)
+ fake_request = object()
+ formset = inline.get_formset(fake_request)
+ self.assertFalse(formset.can_delete)
diff --git a/parts/django/tests/regressiontests/generic_inline_admin/urls.py b/parts/django/tests/regressiontests/generic_inline_admin/urls.py
new file mode 100644
index 0000000..c3e8af8
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_inline_admin/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin.site.urls)),
+)
diff --git a/parts/django/tests/regressiontests/generic_relations_regress/__init__.py b/parts/django/tests/regressiontests/generic_relations_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_relations_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/generic_relations_regress/models.py b/parts/django/tests/regressiontests/generic_relations_regress/models.py
new file mode 100644
index 0000000..d28385d
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_relations_regress/models.py
@@ -0,0 +1,79 @@
+from django.db import models
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+
+__all__ = ('Link', 'Place', 'Restaurant', 'Person', 'Address',
+ 'CharLink', 'TextLink', 'OddRelation1', 'OddRelation2',
+ 'Contact', 'Organization', 'Note')
+
+class Link(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey()
+
+ def __unicode__(self):
+ return "Link to %s id=%s" % (self.content_type, self.object_id)
+
+class Place(models.Model):
+ name = models.CharField(max_length=100)
+ links = generic.GenericRelation(Link)
+
+ def __unicode__(self):
+ return "Place: %s" % self.name
+
+class Restaurant(Place):
+ def __unicode__(self):
+ return "Restaurant: %s" % self.name
+
+class Address(models.Model):
+ street = models.CharField(max_length=80)
+ city = models.CharField(max_length=50)
+ state = models.CharField(max_length=2)
+ zipcode = models.CharField(max_length=5)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey()
+
+ def __unicode__(self):
+ return '%s %s, %s %s' % (self.street, self.city, self.state, self.zipcode)
+
+class Person(models.Model):
+ account = models.IntegerField(primary_key=True)
+ name = models.CharField(max_length=128)
+ addresses = generic.GenericRelation(Address)
+
+ def __unicode__(self):
+ return self.name
+
+class CharLink(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.CharField(max_length=100)
+ content_object = generic.GenericForeignKey()
+
+class TextLink(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.TextField()
+ content_object = generic.GenericForeignKey()
+
+class OddRelation1(models.Model):
+ name = models.CharField(max_length=100)
+ clinks = generic.GenericRelation(CharLink)
+
+class OddRelation2(models.Model):
+ name = models.CharField(max_length=100)
+ tlinks = generic.GenericRelation(TextLink)
+
+# models for test_q_object_or:
+class Note(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey()
+ note = models.TextField()
+
+class Contact(models.Model):
+ notes = generic.GenericRelation(Note)
+
+class Organization(models.Model):
+ name = models.CharField(max_length=255)
+ contacts = models.ManyToManyField(Contact, related_name='organizations')
+
diff --git a/parts/django/tests/regressiontests/generic_relations_regress/tests.py b/parts/django/tests/regressiontests/generic_relations_regress/tests.py
new file mode 100644
index 0000000..45e8674
--- /dev/null
+++ b/parts/django/tests/regressiontests/generic_relations_regress/tests.py
@@ -0,0 +1,74 @@
+from django.test import TestCase
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
+from models import *
+
+class GenericRelationTests(TestCase):
+
+ def test_inherited_models_content_type(self):
+ """
+ Test that GenericRelations on inherited classes use the correct content
+ type.
+ """
+
+ p = Place.objects.create(name="South Park")
+ r = Restaurant.objects.create(name="Chubby's")
+ l1 = Link.objects.create(content_object=p)
+ l2 = Link.objects.create(content_object=r)
+ self.assertEqual(list(p.links.all()), [l1])
+ self.assertEqual(list(r.links.all()), [l2])
+
+ def test_reverse_relation_pk(self):
+ """
+ Test that the correct column name is used for the primary key on the
+ originating model of a query. See #12664.
+ """
+ p = Person.objects.create(account=23, name='Chef')
+ a = Address.objects.create(street='123 Anywhere Place',
+ city='Conifer', state='CO',
+ zipcode='80433', content_object=p)
+
+ qs = Person.objects.filter(addresses__zipcode='80433')
+ self.assertEqual(1, qs.count())
+ self.assertEqual('Chef', qs[0].name)
+
+ def test_charlink_delete(self):
+ oddrel = OddRelation1.objects.create(name='clink')
+ cl = CharLink.objects.create(content_object=oddrel)
+ oddrel.delete()
+
+ def test_textlink_delete(self):
+ oddrel = OddRelation2.objects.create(name='tlink')
+ tl = TextLink.objects.create(content_object=oddrel)
+ oddrel.delete()
+
+ def test_q_object_or(self):
+ """
+ Tests that SQL query parameters for generic relations are properly
+ grouped when OR is used.
+
+ Test for bug http://code.djangoproject.com/ticket/11535
+
+ In this bug the first query (below) works while the second, with the
+ query parameters the same but in reverse order, does not.
+
+ The issue is that the generic relation conditions do not get properly
+ grouped in parentheses.
+ """
+ note_contact = Contact.objects.create()
+ org_contact = Contact.objects.create()
+ note = Note.objects.create(note='note', content_object=note_contact)
+ org = Organization.objects.create(name='org name')
+ org.contacts.add(org_contact)
+ # search with a non-matching note and a matching org name
+ qs = Contact.objects.filter(Q(notes__note__icontains=r'other note') |
+ Q(organizations__name__icontains=r'org name'))
+ self.assertTrue(org_contact in qs)
+ # search again, with the same query parameters, in reverse order
+ qs = Contact.objects.filter(
+ Q(organizations__name__icontains=r'org name') |
+ Q(notes__note__icontains=r'other note'))
+ self.assertTrue(org_contact in qs)
+
+
+
diff --git a/parts/django/tests/regressiontests/get_or_create_regress/__init__.py b/parts/django/tests/regressiontests/get_or_create_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/get_or_create_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/get_or_create_regress/models.py b/parts/django/tests/regressiontests/get_or_create_regress/models.py
new file mode 100644
index 0000000..292d96c
--- /dev/null
+++ b/parts/django/tests/regressiontests/get_or_create_regress/models.py
@@ -0,0 +1,13 @@
+from django.db import models
+
+
+class Publisher(models.Model):
+ name = models.CharField(max_length=100)
+
+class Author(models.Model):
+ name = models.CharField(max_length=100)
+
+class Book(models.Model):
+ name = models.CharField(max_length=100)
+ authors = models.ManyToManyField(Author, related_name='books')
+ publisher = models.ForeignKey(Publisher, related_name='books')
diff --git a/parts/django/tests/regressiontests/get_or_create_regress/tests.py b/parts/django/tests/regressiontests/get_or_create_regress/tests.py
new file mode 100644
index 0000000..87057da
--- /dev/null
+++ b/parts/django/tests/regressiontests/get_or_create_regress/tests.py
@@ -0,0 +1,53 @@
+from django.test import TestCase
+
+from models import Author, Publisher
+
+
+class GetOrCreateTests(TestCase):
+ def test_related(self):
+ p = Publisher.objects.create(name="Acme Publishing")
+ # Create a book through the publisher.
+ book, created = p.books.get_or_create(name="The Book of Ed & Fred")
+ self.assertTrue(created)
+ # The publisher should have one book.
+ self.assertEqual(p.books.count(), 1)
+
+ # Try get_or_create again, this time nothing should be created.
+ book, created = p.books.get_or_create(name="The Book of Ed & Fred")
+ self.assertFalse(created)
+ # And the publisher should still have one book.
+ self.assertEqual(p.books.count(), 1)
+
+ # Add an author to the book.
+ ed, created = book.authors.get_or_create(name="Ed")
+ self.assertTrue(created)
+ # Book should have one author.
+ self.assertEqual(book.authors.count(), 1)
+
+ # Try get_or_create again, this time nothing should be created.
+ ed, created = book.authors.get_or_create(name="Ed")
+ self.assertFalse(created)
+ # And the book should still have one author.
+ self.assertEqual(book.authors.count(), 1)
+
+ # Add a second author to the book.
+ fred, created = book.authors.get_or_create(name="Fred")
+ self.assertTrue(created)
+
+ # The book should have two authors now.
+ self.assertEqual(book.authors.count(), 2)
+
+ # Create an Author not tied to any books.
+ Author.objects.create(name="Ted")
+
+ # There should be three Authors in total. The book object should have two.
+ self.assertEqual(Author.objects.count(), 3)
+ self.assertEqual(book.authors.count(), 2)
+
+ # Try creating a book through an author.
+ _, created = ed.books.get_or_create(name="Ed's Recipes", publisher=p)
+ self.assertTrue(created)
+
+ # Now Ed has two Books, Fred just one.
+ self.assertEqual(ed.books.count(), 2)
+ self.assertEqual(fred.books.count(), 1)
diff --git a/parts/django/tests/regressiontests/httpwrappers/__init__.py b/parts/django/tests/regressiontests/httpwrappers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/httpwrappers/__init__.py
diff --git a/parts/django/tests/regressiontests/httpwrappers/models.py b/parts/django/tests/regressiontests/httpwrappers/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/httpwrappers/models.py
diff --git a/parts/django/tests/regressiontests/httpwrappers/tests.py b/parts/django/tests/regressiontests/httpwrappers/tests.py
new file mode 100644
index 0000000..4e946a2
--- /dev/null
+++ b/parts/django/tests/regressiontests/httpwrappers/tests.py
@@ -0,0 +1,266 @@
+import copy
+import pickle
+import unittest
+
+from django.http import QueryDict, HttpResponse, CompatCookie, BadHeaderError
+
+
+class QueryDictTests(unittest.TestCase):
+ def test_missing_key(self):
+ q = QueryDict('')
+ self.assertRaises(KeyError, q.__getitem__, 'foo')
+
+ def test_immutability(self):
+ q = QueryDict('')
+ self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
+ self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
+ self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
+ self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
+ self.assertRaises(AttributeError, q.pop, 'foo')
+ self.assertRaises(AttributeError, q.popitem)
+ self.assertRaises(AttributeError, q.clear)
+
+ def test_immutable_get_with_default(self):
+ q = QueryDict('')
+ self.assertEqual(q.get('foo', 'default'), 'default')
+
+ def test_immutable_basic_operations(self):
+ q = QueryDict('')
+ self.assertEqual(q.getlist('foo'), [])
+ self.assertEqual(q.has_key('foo'), False)
+ self.assertEqual('foo' in q, False)
+ self.assertEqual(q.items(), [])
+ self.assertEqual(q.lists(), [])
+ self.assertEqual(q.items(), [])
+ self.assertEqual(q.keys(), [])
+ self.assertEqual(q.values(), [])
+ self.assertEqual(len(q), 0)
+ self.assertEqual(q.urlencode(), '')
+
+ def test_single_key_value(self):
+ """Test QueryDict with one key/value pair"""
+
+ q = QueryDict('foo=bar')
+ self.assertEqual(q['foo'], 'bar')
+ self.assertRaises(KeyError, q.__getitem__, 'bar')
+ self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
+
+ self.assertEqual(q.get('foo', 'default'), 'bar')
+ self.assertEqual(q.get('bar', 'default'), 'default')
+ self.assertEqual(q.getlist('foo'), ['bar'])
+ self.assertEqual(q.getlist('bar'), [])
+
+ self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
+ self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
+
+ self.assertTrue(q.has_key('foo'))
+ self.assertTrue('foo' in q)
+ self.assertFalse(q.has_key('bar'))
+ self.assertFalse('bar' in q)
+
+ self.assertEqual(q.items(), [(u'foo', u'bar')])
+ self.assertEqual(q.lists(), [(u'foo', [u'bar'])])
+ self.assertEqual(q.keys(), ['foo'])
+ self.assertEqual(q.values(), ['bar'])
+ self.assertEqual(len(q), 1)
+
+ self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
+ self.assertRaises(AttributeError, q.pop, 'foo')
+ self.assertRaises(AttributeError, q.popitem)
+ self.assertRaises(AttributeError, q.clear)
+ self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
+
+ self.assertEqual(q.urlencode(), 'foo=bar')
+
+ def test_mutable_copy(self):
+ """A copy of a QueryDict is mutable."""
+ q = QueryDict('').copy()
+ self.assertRaises(KeyError, q.__getitem__, "foo")
+ q['name'] = 'john'
+ self.assertEqual(q['name'], 'john')
+
+ def test_mutable_delete(self):
+ q = QueryDict('').copy()
+ q['name'] = 'john'
+ del q['name']
+ self.assertFalse('name' in q)
+
+ def test_basic_mutable_operations(self):
+ q = QueryDict('').copy()
+ q['name'] = 'john'
+ self.assertEqual(q.get('foo', 'default'), 'default')
+ self.assertEqual(q.get('name', 'default'), 'john')
+ self.assertEqual(q.getlist('name'), ['john'])
+ self.assertEqual(q.getlist('foo'), [])
+
+ q.setlist('foo', ['bar', 'baz'])
+ self.assertEqual(q.get('foo', 'default'), 'baz')
+ self.assertEqual(q.getlist('foo'), ['bar', 'baz'])
+
+ q.appendlist('foo', 'another')
+ self.assertEqual(q.getlist('foo'), ['bar', 'baz', 'another'])
+ self.assertEqual(q['foo'], 'another')
+ self.assertTrue(q.has_key('foo'))
+ self.assertTrue('foo' in q)
+
+ self.assertEqual(q.items(), [(u'foo', u'another'), (u'name', u'john')])
+ self.assertEqual(q.lists(), [(u'foo', [u'bar', u'baz', u'another']), (u'name', [u'john'])])
+ self.assertEqual(q.keys(), [u'foo', u'name'])
+ self.assertEqual(q.values(), [u'another', u'john'])
+ self.assertEqual(len(q), 2)
+
+ q.update({'foo': 'hello'})
+ self.assertEqual(q['foo'], 'hello')
+ self.assertEqual(q.get('foo', 'not available'), 'hello')
+ self.assertEqual(q.getlist('foo'), [u'bar', u'baz', u'another', u'hello'])
+ self.assertEqual(q.pop('foo'), [u'bar', u'baz', u'another', u'hello'])
+ self.assertEqual(q.pop('foo', 'not there'), 'not there')
+ self.assertEqual(q.get('foo', 'not there'), 'not there')
+ self.assertEqual(q.setdefault('foo', 'bar'), 'bar')
+ self.assertEqual(q['foo'], 'bar')
+ self.assertEqual(q.getlist('foo'), ['bar'])
+ self.assertEqual(q.urlencode(), 'foo=bar&name=john')
+
+ q.clear()
+ self.assertEqual(len(q), 0)
+
+ def test_multiple_keys(self):
+ """Test QueryDict with two key/value pairs with same keys."""
+
+ q = QueryDict('vote=yes&vote=no')
+
+ self.assertEqual(q['vote'], u'no')
+ self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
+
+ self.assertEqual(q.get('vote', 'default'), u'no')
+ self.assertEqual(q.get('foo', 'default'), 'default')
+ self.assertEqual(q.getlist('vote'), [u'yes', u'no'])
+ self.assertEqual(q.getlist('foo'), [])
+
+ self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
+ self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
+ self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
+
+ self.assertEqual(q.has_key('vote'), True)
+ self.assertEqual('vote' in q, True)
+ self.assertEqual(q.has_key('foo'), False)
+ self.assertEqual('foo' in q, False)
+ self.assertEqual(q.items(), [(u'vote', u'no')])
+ self.assertEqual(q.lists(), [(u'vote', [u'yes', u'no'])])
+ self.assertEqual(q.keys(), [u'vote'])
+ self.assertEqual(q.values(), [u'no'])
+ self.assertEqual(len(q), 1)
+
+ self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
+ self.assertRaises(AttributeError, q.pop, 'foo')
+ self.assertRaises(AttributeError, q.popitem)
+ self.assertRaises(AttributeError, q.clear)
+ self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
+ self.assertRaises(AttributeError, q.__delitem__, 'vote')
+
+ def test_invalid_input_encoding(self):
+ """
+ QueryDicts must be able to handle invalid input encoding (in this
+ case, bad UTF-8 encoding).
+ """
+ q = QueryDict('foo=bar&foo=\xff')
+ self.assertEqual(q['foo'], u'\ufffd')
+ self.assertEqual(q.getlist('foo'), [u'bar', u'\ufffd'])
+
+ def test_pickle(self):
+ q = QueryDict('')
+ q1 = pickle.loads(pickle.dumps(q, 2))
+ self.assertEqual(q == q1, True)
+ q = QueryDict('a=b&c=d')
+ q1 = pickle.loads(pickle.dumps(q, 2))
+ self.assertEqual(q == q1, True)
+ q = QueryDict('a=b&c=d&a=1')
+ q1 = pickle.loads(pickle.dumps(q, 2))
+ self.assertEqual(q == q1 , True)
+
+ def test_update_from_querydict(self):
+ """Regression test for #8278: QueryDict.update(QueryDict)"""
+ x = QueryDict("a=1&a=2", mutable=True)
+ y = QueryDict("a=3&a=4")
+ x.update(y)
+ self.assertEqual(x.getlist('a'), [u'1', u'2', u'3', u'4'])
+
+ def test_non_default_encoding(self):
+ """#13572 - QueryDict with a non-default encoding"""
+ q = QueryDict('sbb=one', encoding='rot_13')
+ self.assertEqual(q.encoding , 'rot_13' )
+ self.assertEqual(q.items() , [(u'foo', u'bar')] )
+ self.assertEqual(q.urlencode() , 'sbb=one' )
+ q = q.copy()
+ self.assertEqual(q.encoding , 'rot_13' )
+ self.assertEqual(q.items() , [(u'foo', u'bar')] )
+ self.assertEqual(q.urlencode() , 'sbb=one' )
+ self.assertEqual(copy.copy(q).encoding , 'rot_13' )
+ self.assertEqual(copy.deepcopy(q).encoding , 'rot_13')
+
+class HttpResponseTests(unittest.TestCase):
+ def test_unicode_headers(self):
+ r = HttpResponse()
+
+ # If we insert a unicode value it will be converted to an ascii
+ r['value'] = u'test value'
+ self.assertTrue(isinstance(r['value'], str))
+ # An error is raised ~hen a unicode object with non-ascii is assigned.
+ self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', u't\xebst value')
+
+ # An error is raised when a unicode object with non-ASCII format is
+ # passed as initial mimetype or content_type.
+ self.assertRaises(UnicodeEncodeError, HttpResponse,
+ mimetype=u't\xebst value')
+
+ # HttpResponse headers must be convertible to ASCII.
+ self.assertRaises(UnicodeEncodeError, HttpResponse,
+ content_type=u't\xebst value')
+
+ # The response also converts unicode keys to strings.)
+ r[u'test'] = 'testing key'
+ l = list(r.items())
+ l.sort()
+ self.assertEqual(l[1], ('test', 'testing key'))
+
+ # It will also raise errors for keys with non-ascii data.
+ self.assertRaises(UnicodeEncodeError, r.__setitem__, u't\xebst key', 'value')
+
+ def test_newlines_in_headers(self):
+ # Bug #10188: Do not allow newlines in headers (CR or LF)
+ r = HttpResponse()
+ self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test')
+ self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test')
+
+class CookieTests(unittest.TestCase):
+ def test_encode(self):
+ """
+ Test that we don't output tricky characters in encoded value
+ """
+ # Python 2.4 compatibility note: Python 2.4's cookie implementation
+ # always returns Set-Cookie headers terminating in semi-colons.
+ # That's not the bug this test is looking for, so ignore it.
+ c = CompatCookie()
+ c['test'] = "An,awkward;value"
+ self.assert_(";" not in c.output().rstrip(';')) # IE compat
+ self.assert_("," not in c.output().rstrip(';')) # Safari compat
+
+ def test_decode(self):
+ """
+ Test that we can still preserve semi-colons and commas
+ """
+ c = CompatCookie()
+ c['test'] = "An,awkward;value"
+ c2 = CompatCookie()
+ c2.load(c.output())
+ self.assertEqual(c['test'].value, c2['test'].value)
+
+ def test_decode_2(self):
+ """
+ Test that we haven't broken normal encoding
+ """
+ c = CompatCookie()
+ c['test'] = "\xf0"
+ c2 = CompatCookie()
+ c2.load(c.output())
+ self.assertEqual(c['test'].value, c2['test'].value)
diff --git a/parts/django/tests/regressiontests/humanize/__init__.py b/parts/django/tests/regressiontests/humanize/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/humanize/__init__.py
diff --git a/parts/django/tests/regressiontests/humanize/models.py b/parts/django/tests/regressiontests/humanize/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/humanize/models.py
diff --git a/parts/django/tests/regressiontests/humanize/tests.py b/parts/django/tests/regressiontests/humanize/tests.py
new file mode 100644
index 0000000..3536c6b
--- /dev/null
+++ b/parts/django/tests/regressiontests/humanize/tests.py
@@ -0,0 +1,76 @@
+import unittest
+from datetime import timedelta, date
+from django.template import Template, Context, add_to_builtins
+from django.utils.dateformat import DateFormat
+from django.utils.translation import ugettext as _
+from django.utils.html import escape
+
+add_to_builtins('django.contrib.humanize.templatetags.humanize')
+
+class HumanizeTests(unittest.TestCase):
+
+ def humanize_tester(self, test_list, result_list, method):
+ # Using max below ensures we go through both lists
+ # However, if the lists are not equal length, this raises an exception
+ for index in xrange(max(len(test_list), len(result_list))):
+ test_content = test_list[index]
+ t = Template('{{ test_content|%s }}' % method)
+ rendered = t.render(Context(locals())).strip()
+ self.assertEqual(rendered, escape(result_list[index]),
+ msg="%s test failed, produced %s, should've produced %s" % (method, rendered, result_list[index]))
+
+ def test_ordinal(self):
+ test_list = ('1','2','3','4','11','12',
+ '13','101','102','103','111',
+ 'something else', None)
+ result_list = ('1st', '2nd', '3rd', '4th', '11th',
+ '12th', '13th', '101st', '102nd', '103rd',
+ '111th', 'something else', None)
+
+ self.humanize_tester(test_list, result_list, 'ordinal')
+
+ def test_intcomma(self):
+ test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25,
+ '100', '1000', '10123', '10311', '1000000', '1234567.1234567',
+ None)
+ result_list = ('100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.25',
+ '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567',
+ None)
+
+ self.humanize_tester(test_list, result_list, 'intcomma')
+
+ def test_intword(self):
+ test_list = ('100', '1000000', '1200000', '1290000',
+ '1000000000','2000000000','6000000000000',
+ None)
+ result_list = ('100', '1.0 million', '1.2 million', '1.3 million',
+ '1.0 billion', '2.0 billion', '6.0 trillion',
+ None)
+
+ self.humanize_tester(test_list, result_list, 'intword')
+
+ def test_apnumber(self):
+ test_list = [str(x) for x in range(1, 11)]
+ test_list.append(None)
+ result_list = (u'one', u'two', u'three', u'four', u'five', u'six',
+ u'seven', u'eight', u'nine', u'10', None)
+
+ self.humanize_tester(test_list, result_list, 'apnumber')
+
+ def test_naturalday(self):
+ from django.template import defaultfilters
+ today = date.today()
+ yesterday = today - timedelta(days=1)
+ tomorrow = today + timedelta(days=1)
+ someday = today - timedelta(days=10)
+ notdate = u"I'm not a date value"
+
+ test_list = (today, yesterday, tomorrow, someday, notdate, None)
+ someday_result = defaultfilters.date(someday)
+ result_list = (_(u'today'), _(u'yesterday'), _(u'tomorrow'),
+ someday_result, u"I'm not a date value", None)
+ self.humanize_tester(test_list, result_list, 'naturalday')
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/parts/django/tests/regressiontests/i18n/__init__.py b/parts/django/tests/regressiontests/i18n/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/__init__.py
diff --git a/parts/django/tests/regressiontests/i18n/forms.py b/parts/django/tests/regressiontests/i18n/forms.py
new file mode 100644
index 0000000..156441c
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/forms.py
@@ -0,0 +1,22 @@
+from django import template, forms
+from django.forms.extras import SelectDateWidget
+from models import Company
+
+class I18nForm(forms.Form):
+ decimal_field = forms.DecimalField(localize=True)
+ float_field = forms.FloatField(localize=True)
+ date_field = forms.DateField(localize=True)
+ datetime_field = forms.DateTimeField(localize=True)
+ time_field = forms.TimeField(localize=True)
+ integer_field = forms.IntegerField(localize=True)
+
+class SelectDateForm(forms.Form):
+ date_field = forms.DateField(widget=SelectDateWidget)
+
+class CompanyForm(forms.ModelForm):
+ cents_payed = forms.DecimalField(max_digits=4, decimal_places=2, localize=True)
+ products_delivered = forms.IntegerField(localize=True)
+ date_added = forms.DateTimeField(localize=True)
+
+ class Meta:
+ model = Company
diff --git a/parts/django/tests/regressiontests/i18n/models.py b/parts/django/tests/regressiontests/i18n/models.py
new file mode 100644
index 0000000..75cd996
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/models.py
@@ -0,0 +1,12 @@
+from datetime import datetime
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+class TestModel(models.Model):
+ text = models.CharField(max_length=10, default=_('Anything'))
+
+class Company(models.Model):
+ name = models.CharField(max_length=50)
+ date_added = models.DateTimeField(default=datetime(1799,1,31,23,59,59,0))
+ cents_payed = models.DecimalField(max_digits=4, decimal_places=2)
+ products_delivered = models.IntegerField()
diff --git a/parts/django/tests/regressiontests/i18n/other/__init__.py b/parts/django/tests/regressiontests/i18n/other/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/other/__init__.py
diff --git a/parts/django/tests/regressiontests/i18n/other/locale/__init__.py b/parts/django/tests/regressiontests/i18n/other/locale/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/other/locale/__init__.py
diff --git a/parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..2bc9343
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000..2fdcee5
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,22 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-14 17:33+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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:3
+msgid "Date/time"
+msgstr "Datum/Zeit (LOCALE_PATHS)"
diff --git a/parts/django/tests/regressiontests/i18n/other/locale/de/__init__.py b/parts/django/tests/regressiontests/i18n/other/locale/de/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/other/locale/de/__init__.py
diff --git a/parts/django/tests/regressiontests/i18n/other/locale/de/formats.py b/parts/django/tests/regressiontests/i18n/other/locale/de/formats.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/other/locale/de/formats.py
diff --git a/parts/django/tests/regressiontests/i18n/resolution/__init__.py b/parts/django/tests/regressiontests/i18n/resolution/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/resolution/__init__.py
diff --git a/parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo b/parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..1c37ac5
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po b/parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000..11d2d5d
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,22 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-14 17:33+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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:3
+msgid "Date/time"
+msgstr "Datum/Zeit (APP)"
diff --git a/parts/django/tests/regressiontests/i18n/resolution/models.py b/parts/django/tests/regressiontests/i18n/resolution/models.py
new file mode 100644
index 0000000..4287ca8
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/resolution/models.py
@@ -0,0 +1 @@
+# \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/i18n/tests.py b/parts/django/tests/regressiontests/i18n/tests.py
new file mode 100644
index 0000000..99f9fe1
--- /dev/null
+++ b/parts/django/tests/regressiontests/i18n/tests.py
@@ -0,0 +1,667 @@
+# -*- encoding: utf-8 -*-
+import datetime
+import decimal
+import os
+import sys
+import pickle
+
+from django.conf import settings
+from django.template import Template, Context
+from django.test import TestCase
+from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules
+from django.utils.numberformat import format as nformat
+from django.utils.safestring import mark_safe, SafeString, SafeUnicode
+from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale
+from django.utils.importlib import import_module
+
+
+from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
+from models import Company, TestModel
+
+
+class TranslationTests(TestCase):
+
+ def test_lazy_objects(self):
+ """
+ Format string interpolation should work with *_lazy objects.
+ """
+ s = ugettext_lazy('Add %(name)s')
+ d = {'name': 'Ringo'}
+ self.assertEqual(u'Add Ringo', s % d)
+ activate('de')
+ try:
+ self.assertEqual(u'Ringo hinzuf\xfcgen', s % d)
+ activate('pl')
+ self.assertEqual(u'Dodaj Ringo', s % d)
+ finally:
+ deactivate()
+
+ # It should be possible to compare *_lazy objects.
+ s1 = ugettext_lazy('Add %(name)s')
+ self.assertEqual(True, s == s1)
+ s2 = gettext_lazy('Add %(name)s')
+ s3 = gettext_lazy('Add %(name)s')
+ self.assertEqual(True, s2 == s3)
+ self.assertEqual(True, s == s2)
+ s4 = ugettext_lazy('Some other string')
+ self.assertEqual(False, s == s4)
+
+ def test_lazy_pickle(self):
+ s1 = ugettext_lazy("test")
+ self.assertEqual(unicode(s1), "test")
+ s2 = pickle.loads(pickle.dumps(s1))
+ self.assertEqual(unicode(s2), "test")
+
+ def test_string_concat(self):
+ """
+ unicode(string_concat(...)) should not raise a TypeError - #4796
+ """
+ import django.utils.translation
+ self.assertEqual(u'django', unicode(django.utils.translation.string_concat("dja", "ngo")))
+
+ def test_safe_status(self):
+ """
+ Translating a string requiring no auto-escaping shouldn't change the "safe" status.
+ """
+ s = mark_safe('Password')
+ self.assertEqual(SafeString, type(s))
+ activate('de')
+ try:
+ self.assertEqual(SafeUnicode, type(ugettext(s)))
+ finally:
+ deactivate()
+ self.assertEqual('aPassword', SafeString('a') + s)
+ self.assertEqual('Passworda', s + SafeString('a'))
+ self.assertEqual('Passworda', s + mark_safe('a'))
+ self.assertEqual('aPassword', mark_safe('a') + s)
+ self.assertEqual('as', mark_safe('a') + mark_safe('s'))
+
+ def test_maclines(self):
+ """
+ Translations on files with mac or dos end of lines will be converted
+ to unix eof in .po catalogs, and they have to match when retrieved
+ """
+ from django.utils.translation.trans_real import translation
+ ca_translation = translation('ca')
+ ca_translation._catalog[u'Mac\nEOF\n'] = u'Catalan Mac\nEOF\n'
+ ca_translation._catalog[u'Win\nEOF\n'] = u'Catalan Win\nEOF\n'
+ activate('ca')
+ try:
+ self.assertEqual(u'Catalan Mac\nEOF\n', ugettext(u'Mac\rEOF\r'))
+ self.assertEqual(u'Catalan Win\nEOF\n', ugettext(u'Win\r\nEOF\r\n'))
+ finally:
+ deactivate()
+
+ def test_to_locale(self):
+ """
+ Tests the to_locale function and the special case of Serbian Latin
+ (refs #12230 and r11299)
+ """
+ self.assertEqual(to_locale('en-us'), 'en_US')
+ self.assertEqual(to_locale('sr-lat'), 'sr_Lat')
+
+ def test_to_language(self):
+ """
+ Test the to_language function
+ """
+ from django.utils.translation.trans_real import to_language
+ self.assertEqual(to_language('en_US'), 'en-us')
+ self.assertEqual(to_language('sr_Lat'), 'sr-lat')
+
+
+class FormattingTests(TestCase):
+
+ def setUp(self):
+ self.use_i18n = settings.USE_I18N
+ self.use_l10n = settings.USE_L10N
+ self.use_thousand_separator = settings.USE_THOUSAND_SEPARATOR
+ self.thousand_separator = settings.THOUSAND_SEPARATOR
+ self.number_grouping = settings.NUMBER_GROUPING
+ self.n = decimal.Decimal('66666.666')
+ self.f = 99999.999
+ self.d = datetime.date(2009, 12, 31)
+ self.dt = datetime.datetime(2009, 12, 31, 20, 50)
+ self.t = datetime.time(10, 15, 48)
+ self.l = 10000L
+ self.ctxt = Context({
+ 'n': self.n,
+ 't': self.t,
+ 'd': self.d,
+ 'dt': self.dt,
+ 'f': self.f,
+ 'l': self.l,
+ })
+
+ def tearDown(self):
+ # Restore defaults
+ settings.USE_I18N = self.use_i18n
+ settings.USE_L10N = self.use_l10n
+ settings.USE_THOUSAND_SEPARATOR = self.use_thousand_separator
+ settings.THOUSAND_SEPARATOR = self.thousand_separator
+ settings.NUMBER_GROUPING = self.number_grouping
+
+ def test_locale_independent(self):
+ """
+ Localization of numbers
+ """
+ settings.USE_L10N = True
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666.66', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
+ self.assertEqual(u'66666A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66,666.66', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
+ self.assertEqual(u'6B6B6B6B6A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
+ self.assertEqual(u'-66666.6', nformat(-66666.666, decimal_sep='.', decimal_pos=1))
+ self.assertEqual(u'-66666.0', nformat(int('-66666'), decimal_sep='.', decimal_pos=1))
+ self.assertEqual(u'10000.0', nformat(self.l, decimal_sep='.', decimal_pos=1))
+
+ # date filter
+ self.assertEqual(u'31.12.2009 в 20:50', Template('{{ dt|date:"d.m.Y в H:i" }}').render(self.ctxt))
+ self.assertEqual(u'⌚ 10:15', Template('{{ t|time:"⌚ H:i" }}').render(self.ctxt))
+
+ def test_l10n_disabled(self):
+ """
+ Catalan locale with format i18n disabled translations will be used,
+ but not formats
+ """
+ settings.USE_L10N = False
+ activate('ca')
+ try:
+ self.assertEqual(u'N j, Y', get_format('DATE_FORMAT'))
+ self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK'))
+ self.assertEqual(u'.', get_format('DECIMAL_SEPARATOR'))
+ self.assertEqual(u'10:15 a.m.', time_format(self.t))
+ self.assertEqual(u'des. 31, 2009', date_format(self.d))
+ self.assertEqual(u'desembre 2009', date_format(self.d, 'YEAR_MONTH_FORMAT'))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT'))
+ self.assertEqual(u'No localizable', localize('No localizable'))
+ self.assertEqual(u'66666.666', localize(self.n))
+ self.assertEqual(u'99999.999', localize(self.f))
+ self.assertEqual(u'10000', localize(self.l))
+ self.assertEqual(u'des. 31, 2009', localize(self.d))
+ self.assertEqual(u'des. 31, 2009, 8:50 p.m.', localize(self.dt))
+ self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt))
+ self.assertEqual(u'99999.999', Template('{{ f }}').render(self.ctxt))
+ self.assertEqual(u'des. 31, 2009', Template('{{ d }}').render(self.ctxt))
+ self.assertEqual(u'des. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt))
+ self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt))
+ self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt))
+ self.assertEqual(u'10:15 a.m.', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt))
+ self.assertEqual(u'12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt))
+
+ form = I18nForm({
+ 'decimal_field': u'66666,666',
+ 'float_field': u'99999,999',
+ 'date_field': u'31/12/2009',
+ 'datetime_field': u'31/12/2009 20:50',
+ 'time_field': u'20:50',
+ 'integer_field': u'1.234',
+ })
+ self.assertEqual(False, form.is_valid())
+ self.assertEqual([u'Introdu\xefu un n\xfamero.'], form.errors['float_field'])
+ self.assertEqual([u'Introdu\xefu un n\xfamero.'], form.errors['decimal_field'])
+ self.assertEqual([u'Introdu\xefu una data v\xe0lida.'], form.errors['date_field'])
+ self.assertEqual([u'Introdu\xefu una data/hora v\xe0lides.'], form.errors['datetime_field'])
+ self.assertEqual([u'Introdu\xefu un n\xfamero sencer.'], form.errors['integer_field'])
+
+ form2 = SelectDateForm({
+ 'date_field_month': u'12',
+ 'date_field_day': u'31',
+ 'date_field_year': u'2009'
+ })
+ self.assertEqual(True, form2.is_valid())
+ self.assertEqual(datetime.date(2009, 12, 31), form2.cleaned_data['date_field'])
+ self.assertEqual(
+ u'<select name="mydate_month" id="id_mydate_month">\n<option value="1">gener</option>\n<option value="2">febrer</option>\n<option value="3">mar\xe7</option>\n<option value="4">abril</option>\n<option value="5">maig</option>\n<option value="6">juny</option>\n<option value="7">juliol</option>\n<option value="8">agost</option>\n<option value="9">setembre</option>\n<option value="10">octubre</option>\n<option value="11">novembre</option>\n<option value="12" selected="selected">desembre</option>\n</select>\n<select name="mydate_day" id="id_mydate_day">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31" selected="selected">31</option>\n</select>\n<select name="mydate_year" id="id_mydate_year">\n<option value="2009" selected="selected">2009</option>\n<option value="2010">2010</option>\n<option value="2011">2011</option>\n<option value="2012">2012</option>\n<option value="2013">2013</option>\n<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n</select>',
+ SelectDateWidget(years=range(2009, 2019)).render('mydate', datetime.date(2009, 12, 31))
+ )
+
+ # We shouldn't change the behavior of the floatformat filter re:
+ # thousand separator and grouping when USE_L10N is False even
+ # if the USE_THOUSAND_SEPARATOR, NUMBER_GROUPING and
+ # THOUSAND_SEPARATOR settings are specified
+ settings.USE_THOUSAND_SEPARATOR = True
+ settings.NUMBER_GROUPING = 1
+ settings.THOUSAND_SEPARATOR = '!'
+ self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt))
+ self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt))
+ finally:
+ deactivate()
+
+ def test_l10n_enabled(self):
+ """
+ Catalan locale
+ """
+ settings.USE_L10N = True
+ activate('ca')
+ try:
+ self.assertEqual('j \de F \de Y', get_format('DATE_FORMAT'))
+ self.assertEqual(1, get_format('FIRST_DAY_OF_WEEK'))
+ self.assertEqual(',', get_format('DECIMAL_SEPARATOR'))
+ self.assertEqual(u'10:15:48', time_format(self.t))
+ self.assertEqual(u'31 de desembre de 2009', date_format(self.d))
+ self.assertEqual(u'desembre del 2009', date_format(self.d, 'YEAR_MONTH_FORMAT'))
+ self.assertEqual(u'31/12/2009 20:50', date_format(self.dt, 'SHORT_DATETIME_FORMAT'))
+ self.assertEqual('No localizable', localize('No localizable'))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66.666,666', localize(self.n))
+ self.assertEqual(u'99.999,999', localize(self.f))
+ self.assertEqual(u'10.000', localize(self.l))
+ self.assertEqual(u'True', localize(True))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666,666', localize(self.n))
+ self.assertEqual(u'99999,999', localize(self.f))
+ self.assertEqual(u'10000', localize(self.l))
+ self.assertEqual(u'31 de desembre de 2009', localize(self.d))
+ self.assertEqual(u'31 de desembre de 2009 a les 20:50', localize(self.dt))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66.666,666', Template('{{ n }}').render(self.ctxt))
+ self.assertEqual(u'99.999,999', Template('{{ f }}').render(self.ctxt))
+ self.assertEqual(u'10.000', Template('{{ l }}').render(self.ctxt))
+
+ form3 = I18nForm({
+ 'decimal_field': u'66.666,666',
+ 'float_field': u'99.999,999',
+ 'date_field': u'31/12/2009',
+ 'datetime_field': u'31/12/2009 20:50',
+ 'time_field': u'20:50',
+ 'integer_field': u'1.234',
+ })
+ self.assertEqual(True, form3.is_valid())
+ self.assertEqual(decimal.Decimal('66666.666'), form3.cleaned_data['decimal_field'])
+ self.assertEqual(99999.999, form3.cleaned_data['float_field'])
+ self.assertEqual(datetime.date(2009, 12, 31), form3.cleaned_data['date_field'])
+ self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form3.cleaned_data['datetime_field'])
+ self.assertEqual(datetime.time(20, 50), form3.cleaned_data['time_field'])
+ self.assertEqual(1234, form3.cleaned_data['integer_field'])
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666,666', Template('{{ n }}').render(self.ctxt))
+ self.assertEqual(u'99999,999', Template('{{ f }}').render(self.ctxt))
+ self.assertEqual(u'31 de desembre de 2009', Template('{{ d }}').render(self.ctxt))
+ self.assertEqual(u'31 de desembre de 2009 a les 20:50', Template('{{ dt }}').render(self.ctxt))
+ self.assertEqual(u'66666,67', Template('{{ n|floatformat:2 }}').render(self.ctxt))
+ self.assertEqual(u'100000,0', Template('{{ f|floatformat }}').render(self.ctxt))
+ self.assertEqual(u'10:15:48', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt))
+ self.assertEqual(u'31/12/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt))
+ self.assertEqual(u'31/12/2009 20:50', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt))
+
+ form4 = I18nForm({
+ 'decimal_field': u'66666,666',
+ 'float_field': u'99999,999',
+ 'date_field': u'31/12/2009',
+ 'datetime_field': u'31/12/2009 20:50',
+ 'time_field': u'20:50',
+ 'integer_field': u'1234',
+ })
+ self.assertEqual(True, form4.is_valid())
+ self.assertEqual(decimal.Decimal('66666.666'), form4.cleaned_data['decimal_field'])
+ self.assertEqual(99999.999, form4.cleaned_data['float_field'])
+ self.assertEqual(datetime.date(2009, 12, 31), form4.cleaned_data['date_field'])
+ self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form4.cleaned_data['datetime_field'])
+ self.assertEqual(datetime.time(20, 50), form4.cleaned_data['time_field'])
+ self.assertEqual(1234, form4.cleaned_data['integer_field'])
+
+ form5 = SelectDateForm({
+ 'date_field_month': u'12',
+ 'date_field_day': u'31',
+ 'date_field_year': u'2009'
+ })
+ self.assertEqual(True, form5.is_valid())
+ self.assertEqual(datetime.date(2009, 12, 31), form5.cleaned_data['date_field'])
+ self.assertEqual(
+ u'<select name="mydate_day" id="id_mydate_day">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31" selected="selected">31</option>\n</select>\n<select name="mydate_month" id="id_mydate_month">\n<option value="1">gener</option>\n<option value="2">febrer</option>\n<option value="3">mar\xe7</option>\n<option value="4">abril</option>\n<option value="5">maig</option>\n<option value="6">juny</option>\n<option value="7">juliol</option>\n<option value="8">agost</option>\n<option value="9">setembre</option>\n<option value="10">octubre</option>\n<option value="11">novembre</option>\n<option value="12" selected="selected">desembre</option>\n</select>\n<select name="mydate_year" id="id_mydate_year">\n<option value="2009" selected="selected">2009</option>\n<option value="2010">2010</option>\n<option value="2011">2011</option>\n<option value="2012">2012</option>\n<option value="2013">2013</option>\n<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n</select>',
+ SelectDateWidget(years=range(2009, 2019)).render('mydate', datetime.date(2009, 12, 31))
+ )
+ finally:
+ deactivate()
+
+ # English locale
+
+ settings.USE_L10N = True
+ activate('en')
+ try:
+ self.assertEqual('N j, Y', get_format('DATE_FORMAT'))
+ self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK'))
+ self.assertEqual('.', get_format('DECIMAL_SEPARATOR'))
+ self.assertEqual(u'Dec. 31, 2009', date_format(self.d))
+ self.assertEqual(u'December 2009', date_format(self.d, 'YEAR_MONTH_FORMAT'))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT'))
+ self.assertEqual(u'No localizable', localize('No localizable'))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66,666.666', localize(self.n))
+ self.assertEqual(u'99,999.999', localize(self.f))
+ self.assertEqual(u'10,000', localize(self.l))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666.666', localize(self.n))
+ self.assertEqual(u'99999.999', localize(self.f))
+ self.assertEqual(u'10000', localize(self.l))
+ self.assertEqual(u'Dec. 31, 2009', localize(self.d))
+ self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', localize(self.dt))
+
+ settings.USE_THOUSAND_SEPARATOR = True
+ self.assertEqual(u'66,666.666', Template('{{ n }}').render(self.ctxt))
+ self.assertEqual(u'99,999.999', Template('{{ f }}').render(self.ctxt))
+ self.assertEqual(u'10,000', Template('{{ l }}').render(self.ctxt))
+
+ settings.USE_THOUSAND_SEPARATOR = False
+ self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt))
+ self.assertEqual(u'99999.999', Template('{{ f }}').render(self.ctxt))
+ self.assertEqual(u'Dec. 31, 2009', Template('{{ d }}').render(self.ctxt))
+ self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt))
+ self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt))
+ self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt))
+ self.assertEqual(u'12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt))
+ self.assertEqual(u'12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt))
+
+ form5 = I18nForm({
+ 'decimal_field': u'66666.666',
+ 'float_field': u'99999.999',
+ 'date_field': u'12/31/2009',
+ 'datetime_field': u'12/31/2009 20:50',
+ 'time_field': u'20:50',
+ 'integer_field': u'1234',
+ })
+ self.assertEqual(True, form5.is_valid())
+ self.assertEqual(decimal.Decimal('66666.666'), form5.cleaned_data['decimal_field'])
+ self.assertEqual(99999.999, form5.cleaned_data['float_field'])
+ self.assertEqual(datetime.date(2009, 12, 31), form5.cleaned_data['date_field'])
+ self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form5.cleaned_data['datetime_field'])
+ self.assertEqual(datetime.time(20, 50), form5.cleaned_data['time_field'])
+ self.assertEqual(1234, form5.cleaned_data['integer_field'])
+
+ form6 = SelectDateForm({
+ 'date_field_month': u'12',
+ 'date_field_day': u'31',
+ 'date_field_year': u'2009'
+ })
+ self.assertEqual(True, form6.is_valid())
+ self.assertEqual(datetime.date(2009, 12, 31), form6.cleaned_data['date_field'])
+ self.assertEqual(
+ u'<select name="mydate_month" id="id_mydate_month">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12" selected="selected">December</option>\n</select>\n<select name="mydate_day" id="id_mydate_day">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31" selected="selected">31</option>\n</select>\n<select name="mydate_year" id="id_mydate_year">\n<option value="2009" selected="selected">2009</option>\n<option value="2010">2010</option>\n<option value="2011">2011</option>\n<option value="2012">2012</option>\n<option value="2013">2013</option>\n<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n</select>',
+ SelectDateWidget(years=range(2009, 2019)).render('mydate', datetime.date(2009, 12, 31))
+ )
+ finally:
+ deactivate()
+
+ def test_sub_locales(self):
+ """
+ Check if sublocales fall back to the main locale
+ """
+ settings.USE_L10N = True
+ activate('de-at')
+ settings.USE_THOUSAND_SEPARATOR = True
+ try:
+ self.assertEqual(u'66.666,666', Template('{{ n }}').render(self.ctxt))
+ finally:
+ deactivate()
+
+ activate('es-us')
+ try:
+ self.assertEqual(u'31 de diciembre de 2009', date_format(self.d))
+ finally:
+ deactivate()
+
+ def test_localized_input(self):
+ """
+ Tests if form input is correctly localized
+ """
+ settings.USE_L10N = True
+ activate('de-at')
+ try:
+ form6 = CompanyForm({
+ 'name': u'acme',
+ 'date_added': datetime.datetime(2009, 12, 31, 6, 0, 0),
+ 'cents_payed': decimal.Decimal('59.47'),
+ 'products_delivered': 12000,
+ })
+ self.assertEqual(True, form6.is_valid())
+ self.assertEqual(
+ form6.as_ul(),
+ u'<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" value="acme" maxlength="50" /></li>\n<li><label for="id_date_added">Date added:</label> <input type="text" name="date_added" value="31.12.2009 06:00:00" id="id_date_added" /></li>\n<li><label for="id_cents_payed">Cents payed:</label> <input type="text" name="cents_payed" value="59,47" id="id_cents_payed" /></li>\n<li><label for="id_products_delivered">Products delivered:</label> <input type="text" name="products_delivered" value="12000" id="id_products_delivered" /></li>'
+ )
+ self.assertEqual(localize_input(datetime.datetime(2009, 12, 31, 6, 0, 0)), '31.12.2009 06:00:00')
+ self.assertEqual(datetime.datetime(2009, 12, 31, 6, 0, 0), form6.cleaned_data['date_added'])
+ settings.USE_THOUSAND_SEPARATOR = True
+ # Checking for the localized "products_delivered" field
+ self.assert_(u'<input type="text" name="products_delivered" value="12.000" id="id_products_delivered" />' in form6.as_ul())
+ finally:
+ deactivate()
+
+ def test_iter_format_modules(self):
+ """
+ Tests the iter_format_modules function.
+ """
+ activate('de-at')
+ old_format_module_path = settings.FORMAT_MODULE_PATH
+ try:
+ settings.USE_L10N = True
+ de_format_mod = import_module('django.conf.locale.de.formats')
+ self.assertEqual(list(iter_format_modules('de')), [de_format_mod])
+ settings.FORMAT_MODULE_PATH = 'regressiontests.i18n.other.locale'
+ test_de_format_mod = import_module('regressiontests.i18n.other.locale.de.formats')
+ self.assertEqual(list(iter_format_modules('de')), [test_de_format_mod, de_format_mod])
+ finally:
+ settings.FORMAT_MODULE_PATH = old_format_module_path
+ deactivate()
+
+
+class MiscTests(TestCase):
+
+ def test_parse_spec_http_header(self):
+ """
+ Testing HTTP header parsing. First, we test that we can parse the
+ values according to the spec (and that we extract all the pieces in
+ the right order).
+ """
+ from django.utils.translation.trans_real import parse_accept_lang_header
+ p = parse_accept_lang_header
+ # Good headers.
+ self.assertEqual([('de', 1.0)], p('de'))
+ self.assertEqual([('en-AU', 1.0)], p('en-AU'))
+ self.assertEqual([('*', 1.0)], p('*;q=1.00'))
+ self.assertEqual([('en-AU', 0.123)], p('en-AU;q=0.123'))
+ self.assertEqual([('en-au', 0.5)], p('en-au;q=0.5'))
+ self.assertEqual([('en-au', 1.0)], p('en-au;q=1.0'))
+ self.assertEqual([('da', 1.0), ('en', 0.5), ('en-gb', 0.25)], p('da, en-gb;q=0.25, en;q=0.5'))
+ self.assertEqual([('en-au-xx', 1.0)], p('en-au-xx'))
+ self.assertEqual([('de', 1.0), ('en-au', 0.75), ('en-us', 0.5), ('en', 0.25), ('es', 0.125), ('fa', 0.125)], p('de,en-au;q=0.75,en-us;q=0.5,en;q=0.25,es;q=0.125,fa;q=0.125'))
+ self.assertEqual([('*', 1.0)], p('*'))
+ self.assertEqual([('de', 1.0)], p('de;q=0.'))
+ self.assertEqual([], p(''))
+
+ # Bad headers; should always return [].
+ self.assertEqual([], p('en-gb;q=1.0000'))
+ self.assertEqual([], p('en;q=0.1234'))
+ self.assertEqual([], p('en;q=.2'))
+ self.assertEqual([], p('abcdefghi-au'))
+ self.assertEqual([], p('**'))
+ self.assertEqual([], p('en,,gb'))
+ self.assertEqual([], p('en-au;q=0.1.0'))
+ self.assertEqual([], p('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ,en'))
+ self.assertEqual([], p('da, en-gb;q=0.8, en;q=0.7,#'))
+ self.assertEqual([], p('de;q=2.0'))
+ self.assertEqual([], p('de;q=0.a'))
+ self.assertEqual([], p(''))
+
+ def test_parse_literal_http_header(self):
+ """
+ Now test that we parse a literal HTTP header correctly.
+ """
+ from django.utils.translation.trans_real import get_language_from_request
+ g = get_language_from_request
+ from django.http import HttpRequest
+ r = HttpRequest
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'}
+ self.assertEqual('pt-br', g(r))
+
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt'}
+ self.assertEqual('pt', g(r))
+
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'es,de'}
+ self.assertEqual('es', g(r))
+
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-ar,de'}
+ self.assertEqual('es-ar', g(r))
+
+ # Python 2.3 and 2.4 return slightly different results for completely
+ # bogus locales, so we omit this test for that anything below 2.4.
+ # It's relatively harmless in any cases (GIGO). This also means this
+ # won't be executed on Jython currently, but life's like that
+ # sometimes. (On those platforms, passing in a truly bogus locale
+ # will get you the default locale back.)
+ if sys.version_info >= (2, 5):
+ # This test assumes there won't be a Django translation to a US
+ # variation of the Spanish language, a safe assumption. When the
+ # user sets it as the preferred language, the main 'es'
+ # translation should be selected instead.
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-us'}
+ self.assertEqual(g(r), 'es')
+
+ # This tests the following scenario: there isn't a main language (zh)
+ # translation of Django but there is a translation to variation (zh_CN)
+ # the user sets zh-cn as the preferred language, it should be selected
+ # by Django without falling back nor ignoring it.
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'zh-cn,de'}
+ self.assertEqual(g(r), 'zh-cn')
+
+ def test_parse_language_cookie(self):
+ """
+ Now test that we parse language preferences stored in a cookie correctly.
+ """
+ from django.utils.translation.trans_real import get_language_from_request
+ g = get_language_from_request
+ from django.http import HttpRequest
+ r = HttpRequest
+ r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'pt-br'}
+ r.META = {}
+ self.assertEqual('pt-br', g(r))
+
+ r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'pt'}
+ r.META = {}
+ self.assertEqual('pt', g(r))
+
+ r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'es'}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'de'}
+ self.assertEqual('es', g(r))
+
+ # Python 2.3 and 2.4 return slightly different results for completely
+ # bogus locales, so we omit this test for that anything below 2.4.
+ # It's relatively harmless in any cases (GIGO). This also means this
+ # won't be executed on Jython currently, but life's like that
+ # sometimes. (On those platforms, passing in a truly bogus locale
+ # will get you the default locale back.)
+ if sys.version_info >= (2, 5):
+ # This test assumes there won't be a Django translation to a US
+ # variation of the Spanish language, a safe assumption. When the
+ # user sets it as the preferred language, the main 'es'
+ # translation should be selected instead.
+ r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'es-us'}
+ r.META = {}
+ self.assertEqual(g(r), 'es')
+
+ # This tests the following scenario: there isn't a main language (zh)
+ # translation of Django but there is a translation to variation (zh_CN)
+ # the user sets zh-cn as the preferred language, it should be selected
+ # by Django without falling back nor ignoring it.
+ r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'zh-cn'}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'de'}
+ self.assertEqual(g(r), 'zh-cn')
+
+class ResolutionOrderI18NTests(TestCase):
+
+ def setUp(self):
+ from django.utils.translation import trans_real
+ # Okay, this is brutal, but we have no other choice to fully reset
+ # the translation framework
+ trans_real._active = {}
+ trans_real._translations = {}
+ activate('de')
+
+ def tearDown(self):
+ deactivate()
+
+ def assertUgettext(self, msgid, msgstr):
+ result = ugettext(msgid)
+ self.assert_(msgstr in result, ("The string '%s' isn't in the "
+ "translation of '%s'; the actual result is '%s'." % (msgstr, msgid, result)))
+
+class AppResolutionOrderI18NTests(ResolutionOrderI18NTests):
+
+ def setUp(self):
+ self.old_installed_apps = settings.INSTALLED_APPS
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution']
+ super(AppResolutionOrderI18NTests, self).setUp()
+
+ def tearDown(self):
+ settings.INSTALLED_APPS = self.old_installed_apps
+ super(AppResolutionOrderI18NTests, self).tearDown()
+
+ def test_app_translation(self):
+ self.assertUgettext('Date/time', 'APP')
+
+class LocalePathsResolutionOrderI18NTests(ResolutionOrderI18NTests):
+
+ def setUp(self):
+ self.old_locale_paths = settings.LOCALE_PATHS
+ settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),)
+ super(LocalePathsResolutionOrderI18NTests, self).setUp()
+
+ def tearDown(self):
+ settings.LOCALE_PATHS = self.old_locale_paths
+ super(LocalePathsResolutionOrderI18NTests, self).tearDown()
+
+ def test_locale_paths_translation(self):
+ self.assertUgettext('Date/time', 'LOCALE_PATHS')
+
+class ProjectResolutionOrderI18NTests(ResolutionOrderI18NTests):
+
+ def setUp(self):
+ self.old_settings_module = settings.SETTINGS_MODULE
+ settings.SETTINGS_MODULE = 'regressiontests'
+ super(ProjectResolutionOrderI18NTests, self).setUp()
+
+ def tearDown(self):
+ settings.SETTINGS_MODULE = self.old_settings_module
+ super(ProjectResolutionOrderI18NTests, self).tearDown()
+
+ def test_project_translation(self):
+ self.assertUgettext('Date/time', 'PROJECT')
+
+ def test_project_override_app_translation(self):
+ old_installed_apps = settings.INSTALLED_APPS
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution']
+ self.assertUgettext('Date/time', 'PROJECT')
+ settings.INSTALLED_APPS = old_installed_apps
+
+ def test_project_override_locale_paths_translation(self):
+ old_locale_paths = settings.LOCALE_PATHS
+ settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),)
+ self.assertUgettext('Date/time', 'PROJECT')
+ settings.LOCALE_PATHS = old_locale_paths
+
+class DjangoFallbackResolutionOrderI18NTests(ResolutionOrderI18NTests):
+
+ def test_django_fallback(self):
+ self.assertUgettext('Date/time', 'Datum/Zeit')
+
+
+class TestModels(TestCase):
+ def test_lazy(self):
+ tm = TestModel()
+ tm.save()
+
+ def test_safestr(self):
+ c = Company(cents_payed=12, products_delivered=1)
+ c.name = SafeUnicode(u'Iñtërnâtiônàlizætiøn1')
+ c.save()
+ c.name = SafeString(u'Iñtërnâtiônàlizætiøn1'.encode('utf-8'))
+ c.save()
diff --git a/parts/django/tests/regressiontests/initial_sql_regress/__init__.py b/parts/django/tests/regressiontests/initial_sql_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/initial_sql_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/initial_sql_regress/models.py b/parts/django/tests/regressiontests/initial_sql_regress/models.py
new file mode 100644
index 0000000..9f91802
--- /dev/null
+++ b/parts/django/tests/regressiontests/initial_sql_regress/models.py
@@ -0,0 +1,11 @@
+"""
+Regression tests for initial SQL insertion.
+"""
+
+from django.db import models
+
+class Simple(models.Model):
+ name = models.CharField(max_length = 50)
+
+# NOTE: The format of the included SQL file for this test suite is important.
+# It must end with a trailing newline in order to test the fix for #2161.
diff --git a/parts/django/tests/regressiontests/initial_sql_regress/sql/simple.sql b/parts/django/tests/regressiontests/initial_sql_regress/sql/simple.sql
new file mode 100644
index 0000000..ca9bd40
--- /dev/null
+++ b/parts/django/tests/regressiontests/initial_sql_regress/sql/simple.sql
@@ -0,0 +1,8 @@
+INSERT INTO initial_sql_regress_simple (name) VALUES ('John');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('Paul');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('George');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man');
+INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending');
+
diff --git a/parts/django/tests/regressiontests/initial_sql_regress/tests.py b/parts/django/tests/regressiontests/initial_sql_regress/tests.py
new file mode 100644
index 0000000..2b3ca91
--- /dev/null
+++ b/parts/django/tests/regressiontests/initial_sql_regress/tests.py
@@ -0,0 +1,8 @@
+from django.test import TestCase
+
+from models import Simple
+
+
+class InitialSQLTests(TestCase):
+ def test_initial_sql(self):
+ self.assertEqual(Simple.objects.count(), 7)
diff --git a/parts/django/tests/regressiontests/inline_formsets/__init__.py b/parts/django/tests/regressiontests/inline_formsets/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/inline_formsets/__init__.py
diff --git a/parts/django/tests/regressiontests/inline_formsets/models.py b/parts/django/tests/regressiontests/inline_formsets/models.py
new file mode 100644
index 0000000..d76eea7
--- /dev/null
+++ b/parts/django/tests/regressiontests/inline_formsets/models.py
@@ -0,0 +1,28 @@
+# coding: utf-8
+from django.db import models
+
+
+class School(models.Model):
+ name = models.CharField(max_length=100)
+
+class Parent(models.Model):
+ name = models.CharField(max_length=100)
+
+class Child(models.Model):
+ mother = models.ForeignKey(Parent, related_name='mothers_children')
+ father = models.ForeignKey(Parent, related_name='fathers_children')
+ school = models.ForeignKey(School)
+ name = models.CharField(max_length=100)
+
+class Poet(models.Model):
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+class Poem(models.Model):
+ poet = models.ForeignKey(Poet)
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
diff --git a/parts/django/tests/regressiontests/inline_formsets/tests.py b/parts/django/tests/regressiontests/inline_formsets/tests.py
new file mode 100644
index 0000000..dd698ab
--- /dev/null
+++ b/parts/django/tests/regressiontests/inline_formsets/tests.py
@@ -0,0 +1,163 @@
+from django.forms.models import inlineformset_factory
+from django.test import TestCase
+
+from regressiontests.inline_formsets.models import Poet, Poem, School, Parent, Child
+
+
+class DeletionTests(TestCase):
+
+ def test_deletion(self):
+ PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
+ poet = Poet.objects.create(name='test')
+ poem = poet.poem_set.create(name='test poem')
+ data = {
+ 'poem_set-TOTAL_FORMS': u'1',
+ 'poem_set-INITIAL_FORMS': u'1',
+ 'poem_set-MAX_NUM_FORMS': u'0',
+ 'poem_set-0-id': str(poem.pk),
+ 'poem_set-0-poet': str(poet.pk),
+ 'poem_set-0-name': u'test',
+ 'poem_set-0-DELETE': u'on',
+ }
+ formset = PoemFormSet(data, instance=poet)
+ formset.save()
+ self.assertTrue(formset.is_valid())
+ self.assertEqual(Poem.objects.count(), 0)
+
+ def test_add_form_deletion_when_invalid(self):
+ """
+ Make sure that an add form that is filled out, but marked for deletion
+ doesn't cause validation errors.
+ """
+ PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
+ poet = Poet.objects.create(name='test')
+ data = {
+ 'poem_set-TOTAL_FORMS': u'1',
+ 'poem_set-INITIAL_FORMS': u'0',
+ 'poem_set-MAX_NUM_FORMS': u'0',
+ 'poem_set-0-id': u'',
+ 'poem_set-0-poem': u'1',
+ 'poem_set-0-name': u'x' * 1000,
+ }
+ formset = PoemFormSet(data, instance=poet)
+ # Make sure this form doesn't pass validation.
+ self.assertEqual(formset.is_valid(), False)
+ self.assertEqual(Poem.objects.count(), 0)
+
+ # Then make sure that it *does* pass validation and delete the object,
+ # even though the data isn't actually valid.
+ data['poem_set-0-DELETE'] = 'on'
+ formset = PoemFormSet(data, instance=poet)
+ self.assertEqual(formset.is_valid(), True)
+ formset.save()
+ self.assertEqual(Poem.objects.count(), 0)
+
+ def test_change_form_deletion_when_invalid(self):
+ """
+ Make sure that a change form that is filled out, but marked for deletion
+ doesn't cause validation errors.
+ """
+ PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
+ poet = Poet.objects.create(name='test')
+ poet.poem_set.create(name='test poem')
+ data = {
+ 'poem_set-TOTAL_FORMS': u'1',
+ 'poem_set-INITIAL_FORMS': u'1',
+ 'poem_set-MAX_NUM_FORMS': u'0',
+ 'poem_set-0-id': u'1',
+ 'poem_set-0-poem': u'1',
+ 'poem_set-0-name': u'x' * 1000,
+ }
+ formset = PoemFormSet(data, instance=poet)
+ # Make sure this form doesn't pass validation.
+ self.assertEqual(formset.is_valid(), False)
+ self.assertEqual(Poem.objects.count(), 1)
+
+ # Then make sure that it *does* pass validation and delete the object,
+ # even though the data isn't actually valid.
+ data['poem_set-0-DELETE'] = 'on'
+ formset = PoemFormSet(data, instance=poet)
+ self.assertEqual(formset.is_valid(), True)
+ formset.save()
+ self.assertEqual(Poem.objects.count(), 0)
+
+ def test_save_new(self):
+ """
+ Make sure inlineformsets respect commit=False
+ regression for #10750
+ """
+ # exclude some required field from the forms
+ ChildFormSet = inlineformset_factory(School, Child, exclude=['father', 'mother'])
+ school = School.objects.create(name=u'test')
+ mother = Parent.objects.create(name=u'mother')
+ father = Parent.objects.create(name=u'father')
+ data = {
+ 'child_set-TOTAL_FORMS': u'1',
+ 'child_set-INITIAL_FORMS': u'0',
+ 'child_set-MAX_NUM_FORMS': u'0',
+ 'child_set-0-name': u'child',
+ }
+ formset = ChildFormSet(data, instance=school)
+ self.assertEqual(formset.is_valid(), True)
+ objects = formset.save(commit=False)
+ for obj in objects:
+ obj.mother = mother
+ obj.father = father
+ obj.save()
+ self.assertEqual(school.child_set.count(), 1)
+
+
+class InlineFormsetFactoryTest(TestCase):
+ def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ def test_inline_formset_factory(self):
+ """
+ These should both work without a problem.
+ """
+ inlineformset_factory(Parent, Child, fk_name='mother')
+ inlineformset_factory(Parent, Child, fk_name='father')
+
+ def test_exception_on_unspecified_foreign_key(self):
+ """
+ Child has two ForeignKeys to Parent, so if we don't specify which one
+ to use for the inline formset, we should get an exception.
+ """
+ self.assertRaisesErrorWithMessage(Exception,
+ "<class 'regressiontests.inline_formsets.models.Child'> has more than 1 ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'>",
+ inlineformset_factory, Parent, Child
+ )
+
+ def test_fk_name_not_foreign_key_field_from_child(self):
+ """
+ If we specify fk_name, but it isn't a ForeignKey from the child model
+ to the parent model, we should get an exception.
+ """
+ self.assertRaisesErrorWithMessage(Exception,
+ "fk_name 'school' is not a ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'>",
+ inlineformset_factory, Parent, Child, fk_name='school'
+ )
+
+ def test_non_foreign_key_field(self):
+ """
+ If the field specified in fk_name is not a ForeignKey, we should get an
+ exception.
+ """
+ self.assertRaisesErrorWithMessage(Exception,
+ "<class 'regressiontests.inline_formsets.models.Child'> has no field named 'test'",
+ inlineformset_factory, Parent, Child, fk_name='test'
+ )
+
+ def test_any_iterable_allowed_as_argument_to_exclude(self):
+ # Regression test for #9171.
+ inlineformset_factory(
+ Parent, Child, exclude=['school'], fk_name='mother'
+ )
+
+ inlineformset_factory(
+ Parent, Child, exclude=('school',), fk_name='mother'
+ )
diff --git a/parts/django/tests/regressiontests/introspection/__init__.py b/parts/django/tests/regressiontests/introspection/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/introspection/__init__.py
diff --git a/parts/django/tests/regressiontests/introspection/models.py b/parts/django/tests/regressiontests/introspection/models.py
new file mode 100644
index 0000000..ef485e3
--- /dev/null
+++ b/parts/django/tests/regressiontests/introspection/models.py
@@ -0,0 +1,21 @@
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=30)
+ email = models.EmailField()
+ facebook_user_id = models.BigIntegerField()
+
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100)
+ pub_date = models.DateField()
+ reporter = models.ForeignKey(Reporter)
+
+ def __unicode__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',) \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/introspection/tests.py b/parts/django/tests/regressiontests/introspection/tests.py
new file mode 100644
index 0000000..2a81aa8
--- /dev/null
+++ b/parts/django/tests/regressiontests/introspection/tests.py
@@ -0,0 +1,111 @@
+from django.conf import settings
+from django.db import connection, DEFAULT_DB_ALIAS
+from django.test import TestCase
+from django.utils import functional
+
+from models import Reporter, Article
+
+#
+# The introspection module is optional, so methods tested here might raise
+# NotImplementedError. This is perfectly acceptable behavior for the backend
+# in question, but the tests need to handle this without failing. Ideally we'd
+# skip these tests, but until #4788 is done we'll just ignore them.
+#
+# The easiest way to accomplish this is to decorate every test case with a
+# wrapper that ignores the exception.
+#
+# The metaclass is just for fun.
+#
+
+def ignore_not_implemented(func):
+ def _inner(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except NotImplementedError:
+ return None
+ functional.update_wrapper(_inner, func)
+ return _inner
+
+class IgnoreNotimplementedError(type):
+ def __new__(cls, name, bases, attrs):
+ for k,v in attrs.items():
+ if k.startswith('test'):
+ attrs[k] = ignore_not_implemented(v)
+ return type.__new__(cls, name, bases, attrs)
+
+class IntrospectionTests(TestCase):
+ __metaclass__ = IgnoreNotimplementedError
+
+ def test_table_names(self):
+ tl = connection.introspection.table_names()
+ self.assert_(Reporter._meta.db_table in tl,
+ "'%s' isn't in table_list()." % Reporter._meta.db_table)
+ self.assert_(Article._meta.db_table in tl,
+ "'%s' isn't in table_list()." % Article._meta.db_table)
+
+ def test_django_table_names(self):
+ cursor = connection.cursor()
+ cursor.execute('CREATE TABLE django_ixn_test_table (id INTEGER);');
+ tl = connection.introspection.django_table_names()
+ cursor.execute("DROP TABLE django_ixn_test_table;")
+ self.assert_('django_ixn_testcase_table' not in tl,
+ "django_table_names() returned a non-Django table")
+
+ def test_installed_models(self):
+ tables = [Article._meta.db_table, Reporter._meta.db_table]
+ models = connection.introspection.installed_models(tables)
+ self.assertEqual(models, set([Article, Reporter]))
+
+ def test_sequence_list(self):
+ sequences = connection.introspection.sequence_list()
+ expected = {'table': Reporter._meta.db_table, 'column': 'id'}
+ self.assert_(expected in sequences,
+ 'Reporter sequence not found in sequence_list()')
+
+ def test_get_table_description_names(self):
+ cursor = connection.cursor()
+ desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
+ self.assertEqual([r[0] for r in desc],
+ [f.column for f in Reporter._meta.fields])
+
+ def test_get_table_description_types(self):
+ cursor = connection.cursor()
+ desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
+ self.assertEqual(
+ [datatype(r[1], r) for r in desc],
+ ['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField']
+ )
+
+ # Regression test for #9991 - 'real' types in postgres
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].startswith('django.db.backends.postgresql'):
+ def test_postgresql_real_type(self):
+ cursor = connection.cursor()
+ cursor.execute("CREATE TABLE django_ixn_real_test_table (number REAL);")
+ desc = connection.introspection.get_table_description(cursor, 'django_ixn_real_test_table')
+ cursor.execute('DROP TABLE django_ixn_real_test_table;')
+ self.assertEqual(datatype(desc[0][1], desc[0]), 'FloatField')
+
+ def test_get_relations(self):
+ cursor = connection.cursor()
+ relations = connection.introspection.get_relations(cursor, Article._meta.db_table)
+
+ # Older versions of MySQL don't have the chops to report on this stuff,
+ # so just skip it if no relations come back. If they do, though, we
+ # should test that the response is correct.
+ if relations:
+ # That's {field_index: (field_index_other_table, other_table)}
+ self.assertEqual(relations, {3: (0, Reporter._meta.db_table)})
+
+ def test_get_indexes(self):
+ cursor = connection.cursor()
+ indexes = connection.introspection.get_indexes(cursor, Article._meta.db_table)
+ self.assertEqual(indexes['reporter_id'], {'unique': False, 'primary_key': False})
+
+
+def datatype(dbtype, description):
+ """Helper to convert a data type into a string."""
+ dt = connection.introspection.get_field_type(dbtype, description)
+ if type(dt) is tuple:
+ return dt[0]
+ else:
+ return dt
diff --git a/parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.mo b/parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..2ee860a
--- /dev/null
+++ b/parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.po b/parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000..356e3d3
--- /dev/null
+++ b/parts/django/tests/regressiontests/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,22 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-14 17:33+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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:3
+msgid "Date/time"
+msgstr "Datum/Zeit (PROJECT)"
diff --git a/parts/django/tests/regressiontests/localflavor/__init__.py b/parts/django/tests/regressiontests/localflavor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/__init__.py
diff --git a/parts/django/tests/regressiontests/localflavor/models.py b/parts/django/tests/regressiontests/localflavor/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/models.py
diff --git a/parts/django/tests/regressiontests/localflavor/tests.py b/parts/django/tests/regressiontests/localflavor/tests.py
new file mode 100644
index 0000000..6968236
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/tests.py
@@ -0,0 +1,5 @@
+import unittest
+from django.test import TestCase
+
+# just import your tests here
+from us.tests import *
diff --git a/parts/django/tests/regressiontests/localflavor/us/__init__.py b/parts/django/tests/regressiontests/localflavor/us/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/us/__init__.py
diff --git a/parts/django/tests/regressiontests/localflavor/us/forms.py b/parts/django/tests/regressiontests/localflavor/us/forms.py
new file mode 100644
index 0000000..9b77e10
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/us/forms.py
@@ -0,0 +1,7 @@
+from django.forms import ModelForm
+from models import USPlace
+
+class USPlaceForm(ModelForm):
+ """docstring for PlaceForm"""
+ class Meta:
+ model = USPlace
diff --git a/parts/django/tests/regressiontests/localflavor/us/models.py b/parts/django/tests/regressiontests/localflavor/us/models.py
new file mode 100644
index 0000000..a8a4cf0
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/us/models.py
@@ -0,0 +1,13 @@
+from django.db import models
+from django.contrib.localflavor.us.models import USStateField
+
+# When creating models you need to remember to add a app_label as
+# 'localflavor', so your model can be found
+
+class USPlace(models.Model):
+ state = USStateField(blank=True)
+ state_req = USStateField()
+ state_default = USStateField(default="CA", blank=True)
+ name = models.CharField(max_length=20)
+ class Meta:
+ app_label = 'localflavor'
diff --git a/parts/django/tests/regressiontests/localflavor/us/tests.py b/parts/django/tests/regressiontests/localflavor/us/tests.py
new file mode 100644
index 0000000..07fe057
--- /dev/null
+++ b/parts/django/tests/regressiontests/localflavor/us/tests.py
@@ -0,0 +1,82 @@
+from django.test import TestCase
+from forms import USPlaceForm
+
+class USLocalflavorTests(TestCase):
+ def setUp(self):
+ self.form = USPlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'})
+
+ def test_get_display_methods(self):
+ """Test that the get_*_display() methods are added to the model instances."""
+ place = self.form.save()
+ self.assertEqual(place.get_state_display(), 'Georgia')
+ self.assertEqual(place.get_state_req_display(), 'North Carolina')
+
+ def test_required(self):
+ """Test that required USStateFields throw appropriate errors."""
+ form = USPlaceForm({'state':'GA', 'name':'Place in GA'})
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors['state_req'], [u'This field is required.'])
+
+ def test_field_blank_option(self):
+ """Test that the empty option is there."""
+ state_select_html = """\
+<select name="state" id="id_state">
+<option value="">---------</option>
+<option value="AL">Alabama</option>
+<option value="AK">Alaska</option>
+<option value="AS">American Samoa</option>
+<option value="AZ">Arizona</option>
+<option value="AR">Arkansas</option>
+<option value="CA">California</option>
+<option value="CO">Colorado</option>
+<option value="CT">Connecticut</option>
+<option value="DE">Delaware</option>
+<option value="DC">District of Columbia</option>
+<option value="FL">Florida</option>
+<option value="GA" selected="selected">Georgia</option>
+<option value="GU">Guam</option>
+<option value="HI">Hawaii</option>
+<option value="ID">Idaho</option>
+<option value="IL">Illinois</option>
+<option value="IN">Indiana</option>
+<option value="IA">Iowa</option>
+<option value="KS">Kansas</option>
+<option value="KY">Kentucky</option>
+<option value="LA">Louisiana</option>
+<option value="ME">Maine</option>
+<option value="MD">Maryland</option>
+<option value="MA">Massachusetts</option>
+<option value="MI">Michigan</option>
+<option value="MN">Minnesota</option>
+<option value="MS">Mississippi</option>
+<option value="MO">Missouri</option>
+<option value="MT">Montana</option>
+<option value="NE">Nebraska</option>
+<option value="NV">Nevada</option>
+<option value="NH">New Hampshire</option>
+<option value="NJ">New Jersey</option>
+<option value="NM">New Mexico</option>
+<option value="NY">New York</option>
+<option value="NC">North Carolina</option>
+<option value="ND">North Dakota</option>
+<option value="MP">Northern Mariana Islands</option>
+<option value="OH">Ohio</option>
+<option value="OK">Oklahoma</option>
+<option value="OR">Oregon</option>
+<option value="PA">Pennsylvania</option>
+<option value="PR">Puerto Rico</option>
+<option value="RI">Rhode Island</option>
+<option value="SC">South Carolina</option>
+<option value="SD">South Dakota</option>
+<option value="TN">Tennessee</option>
+<option value="TX">Texas</option>
+<option value="UT">Utah</option>
+<option value="VT">Vermont</option>
+<option value="VI">Virgin Islands</option>
+<option value="VA">Virginia</option>
+<option value="WA">Washington</option>
+<option value="WV">West Virginia</option>
+<option value="WI">Wisconsin</option>
+<option value="WY">Wyoming</option>
+</select>"""
+ self.assertEqual(str(self.form['state']), state_select_html)
diff --git a/parts/django/tests/regressiontests/m2m_regress/__init__.py b/parts/django/tests/regressiontests/m2m_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/m2m_regress/models.py b/parts/django/tests/regressiontests/m2m_regress/models.py
new file mode 100644
index 0000000..1c2126d
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_regress/models.py
@@ -0,0 +1,58 @@
+from django.db import models
+from django.contrib.auth import models as auth
+
+# No related name is needed here, since symmetrical relations are not
+# explicitly reversible.
+class SelfRefer(models.Model):
+ name = models.CharField(max_length=10)
+ references = models.ManyToManyField('self')
+ related = models.ManyToManyField('self')
+
+ def __unicode__(self):
+ return self.name
+
+class Tag(models.Model):
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return self.name
+
+# Regression for #11956 -- a many to many to the base class
+class TagCollection(Tag):
+ tags = models.ManyToManyField(Tag, related_name='tag_collections')
+
+ def __unicode__(self):
+ return self.name
+
+# A related_name is required on one of the ManyToManyField entries here because
+# they are both addressable as reverse relations from Tag.
+class Entry(models.Model):
+ name = models.CharField(max_length=10)
+ topics = models.ManyToManyField(Tag)
+ related = models.ManyToManyField(Tag, related_name="similar")
+
+ def __unicode__(self):
+ return self.name
+
+# Two models both inheriting from a base model with a self-referential m2m field
+class SelfReferChild(SelfRefer):
+ pass
+
+class SelfReferChildSibling(SelfRefer):
+ pass
+
+# Many-to-Many relation between models, where one of the PK's isn't an Autofield
+class Line(models.Model):
+ name = models.CharField(max_length=100)
+
+class Worksheet(models.Model):
+ id = models.CharField(primary_key=True, max_length=100)
+ lines = models.ManyToManyField(Line, blank=True, null=True)
+
+# Regression for #11226 -- A model with the same name that another one to
+# which it has a m2m relation. This shouldn't cause a name clash between
+# the automatically created m2m intermediary table FK field names when
+# running syncdb
+class User(models.Model):
+ name = models.CharField(max_length=30)
+ friends = models.ManyToManyField(auth.User)
diff --git a/parts/django/tests/regressiontests/m2m_regress/tests.py b/parts/django/tests/regressiontests/m2m_regress/tests.py
new file mode 100644
index 0000000..7e5e5c3
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_regress/tests.py
@@ -0,0 +1,82 @@
+from django.core.exceptions import FieldError
+from django.test import TestCase
+
+from models import (SelfRefer, Tag, TagCollection, Entry, SelfReferChild,
+ SelfReferChildSibling, Worksheet)
+
+
+class M2MRegressionTests(TestCase):
+ def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ def test_multiple_m2m(self):
+ # Multiple m2m references to model must be distinguished when
+ # accessing the relations through an instance attribute.
+
+ s1 = SelfRefer.objects.create(name='s1')
+ s2 = SelfRefer.objects.create(name='s2')
+ s3 = SelfRefer.objects.create(name='s3')
+ s1.references.add(s2)
+ s1.related.add(s3)
+
+ e1 = Entry.objects.create(name='e1')
+ t1 = Tag.objects.create(name='t1')
+ t2 = Tag.objects.create(name='t2')
+
+ e1.topics.add(t1)
+ e1.related.add(t2)
+
+ self.assertQuerysetEqual(s1.references.all(), ["<SelfRefer: s2>"])
+ self.assertQuerysetEqual(s1.related.all(), ["<SelfRefer: s3>"])
+
+ self.assertQuerysetEqual(e1.topics.all(), ["<Tag: t1>"])
+ self.assertQuerysetEqual(e1.related.all(), ["<Tag: t2>"])
+
+ def test_internal_related_name_not_in_error_msg(self):
+ # The secret internal related names for self-referential many-to-many
+ # fields shouldn't appear in the list when an error is made.
+
+ self.assertRaisesErrorWithMessage(FieldError,
+ "Cannot resolve keyword 'porcupine' into field. Choices are: id, name, references, related, selfreferchild, selfreferchildsibling",
+ lambda: SelfRefer.objects.filter(porcupine='fred')
+ )
+
+ def test_m2m_inheritance_symmetry(self):
+ # Test to ensure that the relationship between two inherited models
+ # with a self-referential m2m field maintains symmetry
+
+ sr_child = SelfReferChild(name="Hanna")
+ sr_child.save()
+
+ sr_sibling = SelfReferChildSibling(name="Beth")
+ sr_sibling.save()
+ sr_child.related.add(sr_sibling)
+
+ self.assertQuerysetEqual(sr_child.related.all(), ["<SelfRefer: Beth>"])
+ self.assertQuerysetEqual(sr_sibling.related.all(), ["<SelfRefer: Hanna>"])
+
+ def test_m2m_pk_field_type(self):
+ # Regression for #11311 - The primary key for models in a m2m relation
+ # doesn't have to be an AutoField
+
+ w = Worksheet(id='abc')
+ w.save()
+ w.delete()
+
+ def test_add_m2m_with_base_class(self):
+ # Regression for #11956 -- You can add an object to a m2m with the
+ # base class without causing integrity errors
+
+ t1 = Tag.objects.create(name='t1')
+ t2 = Tag.objects.create(name='t2')
+
+ c1 = TagCollection.objects.create(name='c1')
+ c1.tags = [t1,t2]
+ c1 = TagCollection.objects.get(name='c1')
+
+ self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"])
+ self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"])
diff --git a/parts/django/tests/regressiontests/m2m_through_regress/__init__.py b/parts/django/tests/regressiontests/m2m_through_regress/__init__.py
new file mode 100644
index 0000000..139597f
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_through_regress/__init__.py
@@ -0,0 +1,2 @@
+
+
diff --git a/parts/django/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json b/parts/django/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json
new file mode 100644
index 0000000..6f24886
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json
@@ -0,0 +1,34 @@
+[
+ {
+ "pk": "1",
+ "model": "m2m_through_regress.person",
+ "fields": {
+ "name": "Guido"
+ }
+ },
+ {
+ "pk": "1",
+ "model": "auth.user",
+ "fields": {
+ "username": "Guido",
+ "email": "bdfl@python.org",
+ "password": "abcde"
+ }
+ },
+ {
+ "pk": "1",
+ "model": "m2m_through_regress.group",
+ "fields": {
+ "name": "Python Core Group"
+ }
+ },
+ {
+ "pk": "1",
+ "model": "m2m_through_regress.usermembership",
+ "fields": {
+ "user": "1",
+ "group": "1",
+ "price": "100"
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/m2m_through_regress/models.py b/parts/django/tests/regressiontests/m2m_through_regress/models.py
new file mode 100644
index 0000000..ec87985
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_through_regress/models.py
@@ -0,0 +1,55 @@
+from datetime import datetime
+
+from django.contrib.auth.models import User
+from django.core import management
+from django.db import models
+
+
+# Forward declared intermediate model
+class Membership(models.Model):
+ person = models.ForeignKey('Person')
+ group = models.ForeignKey('Group')
+ price = models.IntegerField(default=100)
+
+ def __unicode__(self):
+ return "%s is a member of %s" % (self.person.name, self.group.name)
+
+# using custom id column to test ticket #11107
+class UserMembership(models.Model):
+ id = models.AutoField(db_column='usermembership_id', primary_key=True)
+ user = models.ForeignKey(User)
+ group = models.ForeignKey('Group')
+ price = models.IntegerField(default=100)
+
+ def __unicode__(self):
+ return "%s is a user and member of %s" % (self.user.username, self.group.name)
+
+class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ def __unicode__(self):
+ return self.name
+
+class Group(models.Model):
+ name = models.CharField(max_length=128)
+ # Membership object defined as a class
+ members = models.ManyToManyField(Person, through=Membership)
+ user_members = models.ManyToManyField(User, through='UserMembership')
+
+ def __unicode__(self):
+ return self.name
+
+# A set of models that use an non-abstract inherited model as the 'through' model.
+class A(models.Model):
+ a_text = models.CharField(max_length=20)
+
+class ThroughBase(models.Model):
+ a = models.ForeignKey(A)
+ b = models.ForeignKey('B')
+
+class Through(ThroughBase):
+ extra = models.CharField(max_length=20)
+
+class B(models.Model):
+ b_text = models.CharField(max_length=20)
+ a_list = models.ManyToManyField(A, through=Through)
diff --git a/parts/django/tests/regressiontests/m2m_through_regress/tests.py b/parts/django/tests/regressiontests/m2m_through_regress/tests.py
new file mode 100644
index 0000000..2eaf886
--- /dev/null
+++ b/parts/django/tests/regressiontests/m2m_through_regress/tests.py
@@ -0,0 +1,128 @@
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from django.core import management
+from django.contrib.auth.models import User
+from django.test import TestCase
+
+from models import Person, Group, Membership, UserMembership
+
+
+class M2MThroughTestCase(TestCase):
+ def test_everything(self):
+ bob = Person.objects.create(name="Bob")
+ jim = Person.objects.create(name="Jim")
+
+ rock = Group.objects.create(name="Rock")
+ roll = Group.objects.create(name="Roll")
+
+ frank = User.objects.create_user("frank", "frank@example.com", "password")
+ jane = User.objects.create_user("jane", "jane@example.com", "password")
+
+ Membership.objects.create(person=bob, group=rock)
+ Membership.objects.create(person=bob, group=roll)
+ Membership.objects.create(person=jim, group=rock)
+
+ self.assertQuerysetEqual(
+ bob.group_set.all(), [
+ "<Group: Rock>",
+ "<Group: Roll>",
+ ]
+ )
+
+ self.assertQuerysetEqual(
+ roll.members.all(), [
+ "<Person: Bob>",
+ ]
+ )
+
+ self.assertRaises(AttributeError, setattr, bob, "group_set", [])
+ self.assertRaises(AttributeError, setattr, roll, "members", [])
+
+ self.assertRaises(AttributeError, rock.members.create, name="Anne")
+ self.assertRaises(AttributeError, bob.group_set.create, name="Funk")
+
+ UserMembership.objects.create(user=frank, group=rock)
+ UserMembership.objects.create(user=frank, group=roll)
+ UserMembership.objects.create(user=jane, group=rock)
+
+ self.assertQuerysetEqual(
+ frank.group_set.all(), [
+ "<Group: Rock>",
+ "<Group: Roll>",
+ ]
+ )
+
+ self.assertQuerysetEqual(
+ roll.user_members.all(), [
+ "<User: frank>",
+ ]
+ )
+
+ def test_serialization(self):
+ "m2m-through models aren't serialized as m2m fields. Refs #8134"
+
+ p = Person.objects.create(name="Bob")
+ g = Group.objects.create(name="Roll")
+ m = Membership.objects.create(person=p, group=g)
+
+ pks = {"p_pk": p.pk, "g_pk": g.pk, "m_pk": m.pk}
+
+ out = StringIO()
+ management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out)
+ self.assertEqual(out.getvalue().strip(), """[{"pk": %(m_pk)s, "model": "m2m_through_regress.membership", "fields": {"person": %(p_pk)s, "price": 100, "group": %(g_pk)s}}, {"pk": %(p_pk)s, "model": "m2m_through_regress.person", "fields": {"name": "Bob"}}, {"pk": %(g_pk)s, "model": "m2m_through_regress.group", "fields": {"name": "Roll"}}]""" % pks)
+
+ out = StringIO()
+ management.call_command("dumpdata", "m2m_through_regress", format="xml",
+ indent=2, stdout=out)
+ self.assertEqual(out.getvalue().strip(), """
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="%(m_pk)s" model="m2m_through_regress.membership">
+ <field to="m2m_through_regress.person" name="person" rel="ManyToOneRel">%(p_pk)s</field>
+ <field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">%(g_pk)s</field>
+ <field type="IntegerField" name="price">100</field>
+ </object>
+ <object pk="%(p_pk)s" model="m2m_through_regress.person">
+ <field type="CharField" name="name">Bob</field>
+ </object>
+ <object pk="%(g_pk)s" model="m2m_through_regress.group">
+ <field type="CharField" name="name">Roll</field>
+ </object>
+</django-objects>
+ """.strip() % pks)
+
+ def test_join_trimming(self):
+ "Check that we don't involve too many copies of the intermediate table when doing a join. Refs #8046, #8254"
+ bob = Person.objects.create(name="Bob")
+ jim = Person.objects.create(name="Jim")
+
+ rock = Group.objects.create(name="Rock")
+ roll = Group.objects.create(name="Roll")
+
+ Membership.objects.create(person=bob, group=rock)
+ Membership.objects.create(person=jim, group=rock, price=50)
+ Membership.objects.create(person=bob, group=roll, price=50)
+
+ self.assertQuerysetEqual(
+ rock.members.filter(membership__price=50), [
+ "<Person: Jim>",
+ ]
+ )
+
+ self.assertQuerysetEqual(
+ bob.group_set.filter(membership__price=50), [
+ "<Group: Roll>",
+ ]
+ )
+
+class ThroughLoadDataTestCase(TestCase):
+ fixtures = ["m2m_through"]
+
+ def test_sequence_creation(self):
+ "Check that sequences on an m2m_through are created for the through model, not a phantom auto-generated m2m table. Refs #11107"
+ out = StringIO()
+ management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out)
+ self.assertEqual(out.getvalue().strip(), """[{"pk": 1, "model": "m2m_through_regress.usermembership", "fields": {"price": 100, "group": 1, "user": 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Guido"}}, {"pk": 1, "model": "m2m_through_regress.group", "fields": {"name": "Python Core Group"}}]""")
diff --git a/parts/django/tests/regressiontests/mail/__init__.py b/parts/django/tests/regressiontests/mail/__init__.py
new file mode 100644
index 0000000..139597f
--- /dev/null
+++ b/parts/django/tests/regressiontests/mail/__init__.py
@@ -0,0 +1,2 @@
+
+
diff --git a/parts/django/tests/regressiontests/mail/custombackend.py b/parts/django/tests/regressiontests/mail/custombackend.py
new file mode 100644
index 0000000..6b0e15a
--- /dev/null
+++ b/parts/django/tests/regressiontests/mail/custombackend.py
@@ -0,0 +1,15 @@
+"""A custom backend for testing."""
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+ def __init__(self, *args, **kwargs):
+ super(EmailBackend, self).__init__(*args, **kwargs)
+ self.test_outbox = []
+
+ def send_messages(self, email_messages):
+ # Messages are stored in a instance variable for testing.
+ self.test_outbox.extend(email_messages)
+ return len(email_messages)
diff --git a/parts/django/tests/regressiontests/mail/models.py b/parts/django/tests/regressiontests/mail/models.py
new file mode 100644
index 0000000..7ff128f
--- /dev/null
+++ b/parts/django/tests/regressiontests/mail/models.py
@@ -0,0 +1 @@
+# This file intentionally left blank \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/mail/tests.py b/parts/django/tests/regressiontests/mail/tests.py
new file mode 100644
index 0000000..877df1c
--- /dev/null
+++ b/parts/django/tests/regressiontests/mail/tests.py
@@ -0,0 +1,375 @@
+# coding: utf-8
+import email
+import os
+import shutil
+import sys
+import tempfile
+from StringIO import StringIO
+from django.conf import settings
+from django.core import mail
+from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
+from django.core.mail import send_mail, send_mass_mail
+from django.core.mail.backends.base import BaseEmailBackend
+from django.core.mail.backends import console, dummy, locmem, filebased, smtp
+from django.core.mail.message import BadHeaderError
+from django.test import TestCase
+from django.utils.translation import ugettext_lazy
+
+class MailTests(TestCase):
+
+ def test_ascii(self):
+ email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'])
+ message = email.message()
+ self.assertEqual(message['Subject'].encode(), 'Subject')
+ self.assertEqual(message.get_payload(), 'Content')
+ self.assertEqual(message['From'], 'from@example.com')
+ self.assertEqual(message['To'], 'to@example.com')
+
+ def test_multiple_recipients(self):
+ email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com','other@example.com'])
+ message = email.message()
+ self.assertEqual(message['Subject'].encode(), 'Subject')
+ self.assertEqual(message.get_payload(), 'Content')
+ self.assertEqual(message['From'], 'from@example.com')
+ self.assertEqual(message['To'], 'to@example.com, other@example.com')
+
+ def test_header_injection(self):
+ email = EmailMessage('Subject\nInjection Test', 'Content', 'from@example.com', ['to@example.com'])
+ self.assertRaises(BadHeaderError, email.message)
+ email = EmailMessage(ugettext_lazy('Subject\nInjection Test'), 'Content', 'from@example.com', ['to@example.com'])
+ self.assertRaises(BadHeaderError, email.message)
+
+ def test_space_continuation(self):
+ """
+ Test for space continuation character in long (ascii) subject headers (#7747)
+ """
+ email = EmailMessage('Long subject lines that get wrapped should use a space continuation character to get expected behaviour in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com'])
+ message = email.message()
+ self.assertEqual(message['Subject'], 'Long subject lines that get wrapped should use a space continuation\n character to get expected behaviour in Outlook and Thunderbird')
+
+ def test_message_header_overrides(self):
+ """
+ Specifying dates or message-ids in the extra headers overrides the
+ default values (#9233)
+ """
+ headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
+ email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers)
+ self.assertEqual(email.message().as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent')
+
+ def test_empty_admins(self):
+ """
+ Test that mail_admins/mail_managers doesn't connect to the mail server
+ if there are no recipients (#9383)
+ """
+ old_admins = settings.ADMINS
+ old_managers = settings.MANAGERS
+
+ settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')]
+ mail.outbox = []
+ mail_admins('hi', 'there')
+ self.assertEqual(len(mail.outbox), 1)
+ mail.outbox = []
+ mail_managers('hi', 'there')
+ self.assertEqual(len(mail.outbox), 1)
+
+ settings.ADMINS = settings.MANAGERS = []
+ mail.outbox = []
+ mail_admins('hi', 'there')
+ self.assertEqual(len(mail.outbox), 0)
+ mail.outbox = []
+ mail_managers('hi', 'there')
+ self.assertEqual(len(mail.outbox), 0)
+
+ settings.ADMINS = old_admins
+ settings.MANAGERS = old_managers
+
+ def test_from_header(self):
+ """
+ Make sure we can manually set the From header (#9214)
+ """
+ email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ message = email.message()
+ self.assertEqual(message['From'], 'from@example.com')
+
+ def test_multiple_message_call(self):
+ """
+ Regression for #13259 - Make sure that headers are not changed when
+ calling EmailMessage.message()
+ """
+ email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ message = email.message()
+ self.assertEqual(message['From'], 'from@example.com')
+ message = email.message()
+ self.assertEqual(message['From'], 'from@example.com')
+
+ def test_unicode_header(self):
+ """
+ Regression for #11144 - When a to/from/cc header contains unicode,
+ make sure the email addresses are parsed correctly (especially with
+ regards to commas)
+ """
+ email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Firstname Sürname" <to@example.com>','other@example.com'])
+ self.assertEqual(email.message()['To'], '=?utf-8?q?Firstname_S=C3=BCrname?= <to@example.com>, other@example.com')
+ email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Sürname, Firstname" <to@example.com>','other@example.com'])
+ self.assertEqual(email.message()['To'], '=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com')
+
+ def test_safe_mime_multipart(self):
+ """
+ Make sure headers can be set with a different encoding than utf-8 in
+ SafeMIMEMultipart as well
+ """
+ headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
+ subject, from_email, to = 'hello', 'from@example.com', '"Sürname, Firstname" <to@example.com>'
+ text_content = 'This is an important message.'
+ html_content = '<p>This is an <strong>important</strong> message.</p>'
+ msg = EmailMultiAlternatives('Message from Firstname Sürname', text_content, from_email, [to], headers=headers)
+ msg.attach_alternative(html_content, "text/html")
+ msg.encoding = 'iso-8859-1'
+ self.assertEqual(msg.message()['To'], '=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>')
+ self.assertEqual(msg.message()['Subject'].encode(), u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=')
+
+ def test_encoding(self):
+ """
+ Regression for #12791 - Encode body correctly with other encodings
+ than utf-8
+ """
+ email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com'])
+ email.encoding = 'iso-8859-1'
+ message = email.message()
+ self.assertTrue(message.as_string().startswith('Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com'))
+ self.assertEqual(message.get_payload(), 'Firstname S=FCrname is a great guy.')
+
+ # Make sure MIME attachments also works correctly with other encodings than utf-8
+ text_content = 'Firstname Sürname is a great guy.'
+ html_content = '<p>Firstname Sürname is a <strong>great</strong> guy.</p>'
+ msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com'])
+ msg.encoding = 'iso-8859-1'
+ msg.attach_alternative(html_content, "text/html")
+ self.assertEqual(msg.message().get_payload(0).as_string(), 'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.')
+ self.assertEqual(msg.message().get_payload(1).as_string(), 'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n<p>Firstname S=FCrname is a <strong>great</strong> guy.</p>')
+
+ def test_attachments(self):
+ """Regression test for #9367"""
+ headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
+ subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
+ text_content = 'This is an important message.'
+ html_content = '<p>This is an <strong>important</strong> message.</p>'
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers)
+ msg.attach_alternative(html_content, "text/html")
+ msg.attach("an attachment.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
+ msg_str = msg.message().as_string()
+ message = email.message_from_string(msg_str)
+ self.assertTrue(message.is_multipart())
+ self.assertEqual(message.get_content_type(), 'multipart/mixed')
+ self.assertEqual(message.get_default_type(), 'text/plain')
+ payload = message.get_payload()
+ self.assertEqual(payload[0].get_content_type(), 'multipart/alternative')
+ self.assertEqual(payload[1].get_content_type(), 'application/pdf')
+
+ def test_arbitrary_stream(self):
+ """
+ Test that the console backend can be pointed at an arbitrary stream.
+ """
+ s = StringIO()
+ connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s)
+ send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
+ self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: '))
+
+ def test_stdout(self):
+ """Make sure that the console backend writes to stdout by default"""
+ old_stdout = sys.stdout
+ sys.stdout = StringIO()
+ connection = console.EmailBackend()
+ email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ connection.send_messages([email])
+ self.assertTrue(sys.stdout.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: '))
+ sys.stdout = old_stdout
+
+ def test_dummy(self):
+ """
+ Make sure that dummy backends returns correct number of sent messages
+ """
+ connection = dummy.EmailBackend()
+ email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ self.assertEqual(connection.send_messages([email, email, email]), 3)
+
+ def test_locmem(self):
+ """
+ Make sure that the locmen backend populates the outbox.
+ """
+ mail.outbox = []
+ connection = locmem.EmailBackend()
+ email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ connection.send_messages([email1, email2])
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertEqual(mail.outbox[0].subject, 'Subject')
+ self.assertEqual(mail.outbox[1].subject, 'Subject 2')
+
+ # Make sure that multiple locmem connections share mail.outbox
+ mail.outbox = []
+ connection2 = locmem.EmailBackend()
+ email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ connection.send_messages([email])
+ connection2.send_messages([email])
+ self.assertEqual(len(mail.outbox), 2)
+
+ def test_file_backend(self):
+ tmp_dir = tempfile.mkdtemp()
+ connection = filebased.EmailBackend(file_path=tmp_dir)
+ email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ connection.send_messages([email1])
+ self.assertEqual(len(os.listdir(tmp_dir)), 1)
+ message = email.message_from_file(open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])))
+ self.assertEqual(message.get_content_type(), 'text/plain')
+ self.assertEqual(message.get('subject'), 'Subject')
+ self.assertEqual(message.get('from'), 'from@example.com')
+ self.assertEqual(message.get('to'), 'to@example.com')
+ connection2 = filebased.EmailBackend(file_path=tmp_dir)
+ connection2.send_messages([email1])
+ self.assertEqual(len(os.listdir(tmp_dir)), 2)
+ connection.send_messages([email1])
+ self.assertEqual(len(os.listdir(tmp_dir)), 2)
+ email1.connection = filebased.EmailBackend(file_path=tmp_dir)
+ connection_created = connection.open()
+ email1.send()
+ self.assertEqual(len(os.listdir(tmp_dir)), 3)
+ email1.send()
+ self.assertEqual(len(os.listdir(tmp_dir)), 3)
+ connection.close()
+ shutil.rmtree(tmp_dir)
+
+ def test_arbitrary_keyword(self):
+ """
+ Make sure that get_connection() accepts arbitrary keyword that might be
+ used with custom backends.
+ """
+ c = mail.get_connection(fail_silently=True, foo='bar')
+ self.assertTrue(c.fail_silently)
+
+ def test_custom_backend(self):
+ """Test custom backend defined in this suite."""
+ conn = mail.get_connection('regressiontests.mail.custombackend.EmailBackend')
+ self.assertTrue(hasattr(conn, 'test_outbox'))
+ email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+ conn.send_messages([email])
+ self.assertEqual(len(conn.test_outbox), 1)
+
+ def test_backend_arg(self):
+ """Test backend argument of mail.get_connection()"""
+ self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend))
+ self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend))
+ self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend))
+ self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend))
+ tmp_dir = tempfile.mkdtemp()
+ self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend))
+ shutil.rmtree(tmp_dir)
+ self.assertTrue(isinstance(mail.get_connection(), locmem.EmailBackend))
+
+ def test_connection_arg(self):
+ """Test connection argument to send_mail(), et. al."""
+ connection = mail.get_connection('django.core.mail.backends.locmem.EmailBackend')
+
+ mail.outbox = []
+ send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
+ self.assertEqual(len(mail.outbox), 1)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, 'Subject')
+ self.assertEqual(message.from_email, 'from@example.com')
+ self.assertEqual(message.to, ['to@example.com'])
+
+ mail.outbox = []
+ send_mass_mail([
+ ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']),
+ ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com'])
+ ], connection=connection)
+ self.assertEqual(len(mail.outbox), 2)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, 'Subject1')
+ self.assertEqual(message.from_email, 'from1@example.com')
+ self.assertEqual(message.to, ['to1@example.com'])
+ message = mail.outbox[1]
+ self.assertEqual(message.subject, 'Subject2')
+ self.assertEqual(message.from_email, 'from2@example.com')
+ self.assertEqual(message.to, ['to2@example.com'])
+
+ old_admins = settings.ADMINS
+ old_managers = settings.MANAGERS
+ settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')]
+
+ mail.outbox = []
+ mail_admins('Subject', 'Content', connection=connection)
+ self.assertEqual(len(mail.outbox), 1)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, '[Django] Subject')
+ self.assertEqual(message.from_email, 'root@localhost')
+ self.assertEqual(message.to, ['nobody@example.com'])
+
+ mail.outbox = []
+ mail_managers('Subject', 'Content', connection=connection)
+ self.assertEqual(len(mail.outbox), 1)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, '[Django] Subject')
+ self.assertEqual(message.from_email, 'root@localhost')
+ self.assertEqual(message.to, ['nobody@example.com'])
+
+ settings.ADMINS = old_admins
+ settings.MANAGERS = old_managers
+
+ def test_mail_prefix(self):
+ """Test prefix argument in manager/admin mail."""
+ # Regression for #13494.
+ old_admins = settings.ADMINS
+ old_managers = settings.MANAGERS
+ settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')]
+
+ mail_managers(ugettext_lazy('Subject'), 'Content')
+ self.assertEqual(len(mail.outbox), 1)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, '[Django] Subject')
+
+ mail.outbox = []
+ mail_admins(ugettext_lazy('Subject'), 'Content')
+ self.assertEqual(len(mail.outbox), 1)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, '[Django] Subject')
+
+ settings.ADMINS = old_admins
+ settings.MANAGERS = old_managers
+
+ def test_idn_validation(self):
+ """Test internationalized email adresses"""
+ # Regression for #14301.
+ mail.outbox = []
+ from_email = u'fröm@öäü.com'
+ to_email = u'tö@öäü.com'
+ connection = mail.get_connection('django.core.mail.backends.locmem.EmailBackend')
+ send_mail('Subject', 'Content', from_email, [to_email], connection=connection)
+ self.assertEqual(len(mail.outbox), 1)
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, 'Subject')
+ self.assertEqual(message.from_email, from_email)
+ self.assertEqual(message.to, [to_email])
+ self.assertTrue(message.message().as_string().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: =?utf-8?b?ZnLDtm1Aw7bDpMO8LmNvbQ==?=\nTo: =?utf-8?b?dMO2QMO2w6TDvC5jb20=?='))
+
+ def test_idn_smtp_send(self):
+ import smtplib
+ smtplib.SMTP = MockSMTP
+ from_email = u'fröm@öäü.com'
+ to_email = u'tö@öäü.com'
+ connection = mail.get_connection('django.core.mail.backends.smtp.EmailBackend')
+ self.assertTrue(send_mail('Subject', 'Content', from_email, [to_email], connection=connection))
+
+class MockSMTP(object):
+ def __init__(self, host='', port=0, local_hostname=None,
+ timeout=1):
+ pass
+
+ def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
+ rcpt_options=[]):
+ for addr in to_addrs:
+ str(addr.split('@', 1)[-1])
+ return {}
+
+ def quit(self):
+ return 0
diff --git a/parts/django/tests/regressiontests/makemessages/__init__.py b/parts/django/tests/regressiontests/makemessages/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/__init__.py
diff --git a/parts/django/tests/regressiontests/makemessages/extraction.py b/parts/django/tests/regressiontests/makemessages/extraction.py
new file mode 100644
index 0000000..6df6e90
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/extraction.py
@@ -0,0 +1,109 @@
+import os
+import re
+import shutil
+from django.test import TestCase
+from django.core import management
+
+LOCALE='de'
+
+class ExtractorTests(TestCase):
+
+ PO_FILE='locale/%s/LC_MESSAGES/django.po' % LOCALE
+
+ def setUp(self):
+ self._cwd = os.getcwd()
+ self.test_dir = os.path.abspath(os.path.dirname(__file__))
+
+ def _rmrf(self, dname):
+ if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir:
+ return
+ shutil.rmtree(dname)
+
+ def tearDown(self):
+ os.chdir(self.test_dir)
+ try:
+ self._rmrf('locale/%s' % LOCALE)
+ except OSError:
+ pass
+ os.chdir(self._cwd)
+
+ def assertMsgId(self, msgid, s):
+ return self.assert_(re.search('^msgid "%s"' % msgid, s, re.MULTILINE))
+
+ def assertNotMsgId(self, msgid, s):
+ return self.assert_(not re.search('^msgid "%s"' % msgid, s, re.MULTILINE))
+
+
+class TemplateExtractorTests(ExtractorTests):
+
+ def test_templatize(self):
+ os.chdir(self.test_dir)
+ management.call_command('makemessages', locale=LOCALE, verbosity=0)
+ self.assert_(os.path.exists(self.PO_FILE))
+ po_contents = open(self.PO_FILE, 'r').read()
+ self.assertMsgId('I think that 100%% is more that 50%% of anything.', po_contents)
+ self.assertMsgId('I think that 100%% is more that 50%% of %\(obj\)s.', po_contents)
+
+
+class JavascriptExtractorTests(ExtractorTests):
+
+ PO_FILE='locale/%s/LC_MESSAGES/djangojs.po' % LOCALE
+
+ def test_javascript_literals(self):
+ os.chdir(self.test_dir)
+ management.call_command('makemessages', domain='djangojs', locale=LOCALE, verbosity=0)
+ self.assert_(os.path.exists(self.PO_FILE))
+ po_contents = open(self.PO_FILE, 'r').read()
+ self.assertMsgId('This literal should be included.', po_contents)
+ self.assertMsgId('This one as well.', po_contents)
+
+
+class IgnoredExtractorTests(ExtractorTests):
+
+ def test_ignore_option(self):
+ os.chdir(self.test_dir)
+ management.call_command('makemessages', locale=LOCALE, verbosity=0, ignore_patterns=['ignore_dir/*'])
+ self.assert_(os.path.exists(self.PO_FILE))
+ po_contents = open(self.PO_FILE, 'r').read()
+ self.assertMsgId('This literal should be included.', po_contents)
+ self.assertNotMsgId('This should be ignored.', po_contents)
+
+
+class SymlinkExtractorTests(ExtractorTests):
+
+ def setUp(self):
+ self._cwd = os.getcwd()
+ self.test_dir = os.path.abspath(os.path.dirname(__file__))
+ self.symlinked_dir = os.path.join(self.test_dir, 'templates_symlinked')
+
+ def tearDown(self):
+ super(SymlinkExtractorTests, self).tearDown()
+ os.chdir(self.test_dir)
+ try:
+ os.remove(self.symlinked_dir)
+ except OSError:
+ pass
+ os.chdir(self._cwd)
+
+ def test_symlink(self):
+ if hasattr(os, 'symlink'):
+ if os.path.exists(self.symlinked_dir):
+ self.assert_(os.path.islink(self.symlinked_dir))
+ else:
+ os.symlink(os.path.join(self.test_dir, 'templates'), self.symlinked_dir)
+ os.chdir(self.test_dir)
+ management.call_command('makemessages', locale=LOCALE, verbosity=0, symlinks=True)
+ self.assert_(os.path.exists(self.PO_FILE))
+ po_contents = open(self.PO_FILE, 'r').read()
+ self.assertMsgId('This literal should be included.', po_contents)
+ self.assert_('templates_symlinked/test.html' in po_contents)
+
+
+class CopyPluralFormsExtractorTests(ExtractorTests):
+
+ def test_copy_plural_forms(self):
+ os.chdir(self.test_dir)
+ management.call_command('makemessages', locale=LOCALE, verbosity=0)
+ self.assert_(os.path.exists(self.PO_FILE))
+ po_contents = open(self.PO_FILE, 'r').read()
+ self.assert_('Plural-Forms: nplurals=2; plural=(n != 1)' in po_contents)
diff --git a/parts/django/tests/regressiontests/makemessages/ignore_dir/ignored.html b/parts/django/tests/regressiontests/makemessages/ignore_dir/ignored.html
new file mode 100644
index 0000000..6a29678
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/ignore_dir/ignored.html
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% trans "This should be ignored." %}
diff --git a/parts/django/tests/regressiontests/makemessages/javascript.js b/parts/django/tests/regressiontests/makemessages/javascript.js
new file mode 100644
index 0000000..bc5ec87
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/javascript.js
@@ -0,0 +1,4 @@
+// '
+gettext('This literal should be included.')
+// '
+gettext('This one as well.')
diff --git a/parts/django/tests/regressiontests/makemessages/locale/dummy b/parts/django/tests/regressiontests/makemessages/locale/dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/locale/dummy
diff --git a/parts/django/tests/regressiontests/makemessages/models.py b/parts/django/tests/regressiontests/makemessages/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/models.py
diff --git a/parts/django/tests/regressiontests/makemessages/templates/test.html b/parts/django/tests/regressiontests/makemessages/templates/test.html
new file mode 100644
index 0000000..96438e1
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/templates/test.html
@@ -0,0 +1,4 @@
+{% load i18n %}
+{% trans "This literal should be included." %}
+{% blocktrans %}I think that 100% is more that 50% of anything.{% endblocktrans %}
+{% blocktrans with 'txt' as obj %}I think that 100% is more that 50% of {{ obj }}.{% endblocktrans %} \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/makemessages/tests.py b/parts/django/tests/regressiontests/makemessages/tests.py
new file mode 100644
index 0000000..5798e67
--- /dev/null
+++ b/parts/django/tests/regressiontests/makemessages/tests.py
@@ -0,0 +1,40 @@
+import os
+import re
+from subprocess import Popen, PIPE
+
+def find_command(cmd, path=None, pathext=None):
+ if path is None:
+ path = os.environ.get('PATH', []).split(os.pathsep)
+ if isinstance(path, basestring):
+ path = [path]
+ # check if there are funny path extensions for executables, e.g. Windows
+ if pathext is None:
+ pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD').split(os.pathsep)
+ # don't use extensions if the command ends with one of them
+ for ext in pathext:
+ if cmd.endswith(ext):
+ pathext = ['']
+ break
+ # check if we find the command on PATH
+ for p in path:
+ f = os.path.join(p, cmd)
+ if os.path.isfile(f):
+ return f
+ for ext in pathext:
+ fext = f + ext
+ if os.path.isfile(fext):
+ return fext
+ return None
+
+# checks if it can find xgettext on the PATH and
+# imports the extraction tests if yes
+xgettext_cmd = find_command('xgettext')
+if xgettext_cmd:
+ p = Popen('%s --version' % xgettext_cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True)
+ output = p.communicate()[0]
+ match = re.search(r'(?P<major>\d+)\.(?P<minor>\d+)', output)
+ if match:
+ xversion = (int(match.group('major')), int(match.group('minor')))
+ if xversion >= (0, 15):
+ from extraction import *
+ del p
diff --git a/parts/django/tests/regressiontests/managers_regress/__init__.py b/parts/django/tests/regressiontests/managers_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/managers_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/managers_regress/models.py b/parts/django/tests/regressiontests/managers_regress/models.py
new file mode 100644
index 0000000..1e1b1c9
--- /dev/null
+++ b/parts/django/tests/regressiontests/managers_regress/models.py
@@ -0,0 +1,100 @@
+"""
+Various edge-cases for model managers.
+"""
+
+from django.db import models
+
+class OnlyFred(models.Manager):
+ def get_query_set(self):
+ return super(OnlyFred, self).get_query_set().filter(name='fred')
+
+class OnlyBarney(models.Manager):
+ def get_query_set(self):
+ return super(OnlyBarney, self).get_query_set().filter(name='barney')
+
+class Value42(models.Manager):
+ def get_query_set(self):
+ return super(Value42, self).get_query_set().filter(value=42)
+
+class AbstractBase1(models.Model):
+ name = models.CharField(max_length=50)
+
+ class Meta:
+ abstract = True
+
+ # Custom managers
+ manager1 = OnlyFred()
+ manager2 = OnlyBarney()
+ objects = models.Manager()
+
+class AbstractBase2(models.Model):
+ value = models.IntegerField()
+
+ class Meta:
+ abstract = True
+
+ # Custom manager
+ restricted = Value42()
+
+# No custom manager on this class to make sure the default case doesn't break.
+class AbstractBase3(models.Model):
+ comment = models.CharField(max_length=50)
+
+ class Meta:
+ abstract = True
+
+class Parent(models.Model):
+ name = models.CharField(max_length=50)
+
+ manager = OnlyFred()
+
+ def __unicode__(self):
+ return self.name
+
+# Managers from base classes are inherited and, if no manager is specified
+# *and* the parent has a manager specified, the first one (in the MRO) will
+# become the default.
+class Child1(AbstractBase1):
+ data = models.CharField(max_length=25)
+
+ def __unicode__(self):
+ return self.data
+
+class Child2(AbstractBase1, AbstractBase2):
+ data = models.CharField(max_length=25)
+
+ def __unicode__(self):
+ return self.data
+
+class Child3(AbstractBase1, AbstractBase3):
+ data = models.CharField(max_length=25)
+
+ def __unicode__(self):
+ return self.data
+
+class Child4(AbstractBase1):
+ data = models.CharField(max_length=25)
+
+ # Should be the default manager, although the parent managers are
+ # inherited.
+ default = models.Manager()
+
+ def __unicode__(self):
+ return self.data
+
+class Child5(AbstractBase3):
+ name = models.CharField(max_length=25)
+
+ default = OnlyFred()
+ objects = models.Manager()
+
+ def __unicode__(self):
+ return self.name
+
+# Will inherit managers from AbstractBase1, but not Child4.
+class Child6(Child4):
+ value = models.IntegerField()
+
+# Will not inherit default manager from parent.
+class Child7(Parent):
+ pass
diff --git a/parts/django/tests/regressiontests/managers_regress/tests.py b/parts/django/tests/regressiontests/managers_regress/tests.py
new file mode 100644
index 0000000..9a6db61
--- /dev/null
+++ b/parts/django/tests/regressiontests/managers_regress/tests.py
@@ -0,0 +1,54 @@
+from django.test import TestCase
+
+from models import Child1, Child2, Child3, Child4, Child5, Child6, Child7
+
+
+class ManagersRegressionTests(TestCase):
+ def test_managers(self):
+ a1 = Child1.objects.create(name='fred', data='a1')
+ a2 = Child1.objects.create(name='barney', data='a2')
+ b1 = Child2.objects.create(name='fred', data='b1', value=1)
+ b2 = Child2.objects.create(name='barney', data='b2', value=42)
+ c1 = Child3.objects.create(name='fred', data='c1', comment='yes')
+ c2 = Child3.objects.create(name='barney', data='c2', comment='no')
+ d1 = Child4.objects.create(name='fred', data='d1')
+ d2 = Child4.objects.create(name='barney', data='d2')
+ e1 = Child5.objects.create(name='fred', comment='yes')
+ e2 = Child5.objects.create(name='barney', comment='no')
+ f1 = Child6.objects.create(name='fred', data='f1', value=42)
+ f2 = Child6.objects.create(name='barney', data='f2', value=42)
+ g1 = Child7.objects.create(name='fred')
+ g2 = Child7.objects.create(name='barney')
+
+ self.assertQuerysetEqual(Child1.manager1.all(), ["<Child1: a1>"])
+ self.assertQuerysetEqual(Child1.manager2.all(), ["<Child1: a2>"])
+ self.assertQuerysetEqual(Child1._default_manager.all(), ["<Child1: a1>"])
+
+ self.assertQuerysetEqual(Child2._default_manager.all(), ["<Child2: b1>"])
+ self.assertQuerysetEqual(Child2.restricted.all(), ["<Child2: b2>"])
+
+ self.assertQuerysetEqual(Child3._default_manager.all(), ["<Child3: c1>"])
+ self.assertQuerysetEqual(Child3.manager1.all(), ["<Child3: c1>"])
+ self.assertQuerysetEqual(Child3.manager2.all(), ["<Child3: c2>"])
+
+ # Since Child6 inherits from Child4, the corresponding rows from f1 and
+ # f2 also appear here. This is the expected result.
+ self.assertQuerysetEqual(Child4._default_manager.order_by('data'), [
+ "<Child4: d1>",
+ "<Child4: d2>",
+ "<Child4: f1>",
+ "<Child4: f2>"
+ ]
+ )
+ self.assertQuerysetEqual(Child4.manager1.all(), [
+ "<Child4: d1>",
+ "<Child4: f1>"
+ ]
+ )
+ self.assertQuerysetEqual(Child5._default_manager.all(), ["<Child5: fred>"])
+ self.assertQuerysetEqual(Child6._default_manager.all(), ["<Child6: f1>"])
+ self.assertQuerysetEqual(Child7._default_manager.order_by('name'), [
+ "<Child7: barney>",
+ "<Child7: fred>"
+ ]
+ )
diff --git a/parts/django/tests/regressiontests/many_to_one_regress/__init__.py b/parts/django/tests/regressiontests/many_to_one_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/many_to_one_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/many_to_one_regress/models.py b/parts/django/tests/regressiontests/many_to_one_regress/models.py
new file mode 100644
index 0000000..53348a7
--- /dev/null
+++ b/parts/django/tests/regressiontests/many_to_one_regress/models.py
@@ -0,0 +1,46 @@
+"""
+Regression tests for a few ForeignKey bugs.
+"""
+
+from django.db import models
+
+# If ticket #1578 ever slips back in, these models will not be able to be
+# created (the field names being lower-cased versions of their opposite
+# classes is important here).
+
+class First(models.Model):
+ second = models.IntegerField()
+
+class Second(models.Model):
+ first = models.ForeignKey(First, related_name = 'the_first')
+
+# Protect against repetition of #1839, #2415 and #2536.
+class Third(models.Model):
+ name = models.CharField(max_length=20)
+ third = models.ForeignKey('self', null=True, related_name='child_set')
+
+class Parent(models.Model):
+ name = models.CharField(max_length=20)
+ bestchild = models.ForeignKey('Child', null=True, related_name='favored_by')
+
+class Child(models.Model):
+ name = models.CharField(max_length=20)
+ parent = models.ForeignKey(Parent)
+
+
+# Multiple paths to the same model (#7110, #7125)
+class Category(models.Model):
+ name = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return self.name
+
+class Record(models.Model):
+ category = models.ForeignKey(Category)
+
+class Relation(models.Model):
+ left = models.ForeignKey(Record, related_name='left_set')
+ right = models.ForeignKey(Record, related_name='right_set')
+
+ def __unicode__(self):
+ return u"%s - %s" % (self.left.category.name, self.right.category.name)
diff --git a/parts/django/tests/regressiontests/many_to_one_regress/tests.py b/parts/django/tests/regressiontests/many_to_one_regress/tests.py
new file mode 100644
index 0000000..7d2a49c
--- /dev/null
+++ b/parts/django/tests/regressiontests/many_to_one_regress/tests.py
@@ -0,0 +1,105 @@
+from django.db import models
+from django.test import TestCase
+
+from models import First, Second, Third, Parent, Child, Category, Record, Relation
+
+class ManyToOneRegressionTests(TestCase):
+ def test_object_creation(self):
+ Third.objects.create(id='3', name='An example')
+ parent = Parent(name='fred')
+ parent.save()
+ Child.objects.create(name='bam-bam', parent=parent)
+
+ def test_fk_assignment_and_related_object_cache(self):
+ # Tests of ForeignKey assignment and the related-object cache (see #6886).
+
+ p = Parent.objects.create(name="Parent")
+ c = Child.objects.create(name="Child", parent=p)
+
+ # Look up the object again so that we get a "fresh" object.
+ c = Child.objects.get(name="Child")
+ p = c.parent
+
+ # Accessing the related object again returns the exactly same object.
+ self.assertTrue(c.parent is p)
+
+ # But if we kill the cache, we get a new object.
+ del c._parent_cache
+ self.assertFalse(c.parent is p)
+
+ # Assigning a new object results in that object getting cached immediately.
+ p2 = Parent.objects.create(name="Parent 2")
+ c.parent = p2
+ self.assertTrue(c.parent is p2)
+
+ # Assigning None succeeds if field is null=True.
+ p.bestchild = None
+ self.assertTrue(p.bestchild is None)
+
+ # bestchild should still be None after saving.
+ p.save()
+ self.assertTrue(p.bestchild is None)
+
+ # bestchild should still be None after fetching the object again.
+ p = Parent.objects.get(name="Parent")
+ self.assertTrue(p.bestchild is None)
+
+ # Assigning None fails: Child.parent is null=False.
+ self.assertRaises(ValueError, setattr, c, "parent", None)
+
+ # You also can't assign an object of the wrong type here
+ self.assertRaises(ValueError, setattr, c, "parent", First(id=1, second=1))
+
+ # Nor can you explicitly assign None to Child.parent during object
+ # creation (regression for #9649).
+ self.assertRaises(ValueError, Child, name='xyzzy', parent=None)
+ self.assertRaises(ValueError, Child.objects.create, name='xyzzy', parent=None)
+
+ # Creation using keyword argument should cache the related object.
+ p = Parent.objects.get(name="Parent")
+ c = Child(parent=p)
+ self.assertTrue(c.parent is p)
+
+ # Creation using keyword argument and unsaved related instance (#8070).
+ p = Parent()
+ c = Child(parent=p)
+ self.assertTrue(c.parent is p)
+
+ # Creation using attname keyword argument and an id will cause the
+ # related object to be fetched.
+ p = Parent.objects.get(name="Parent")
+ c = Child(parent_id=p.id)
+ self.assertFalse(c.parent is p)
+ self.assertEqual(c.parent, p)
+
+ def test_multiple_foreignkeys(self):
+ # Test of multiple ForeignKeys to the same model (bug #7125).
+ c1 = Category.objects.create(name='First')
+ c2 = Category.objects.create(name='Second')
+ c3 = Category.objects.create(name='Third')
+ r1 = Record.objects.create(category=c1)
+ r2 = Record.objects.create(category=c1)
+ r3 = Record.objects.create(category=c2)
+ r4 = Record.objects.create(category=c2)
+ r5 = Record.objects.create(category=c3)
+ r = Relation.objects.create(left=r1, right=r2)
+ r = Relation.objects.create(left=r3, right=r4)
+ r = Relation.objects.create(left=r1, right=r3)
+ r = Relation.objects.create(left=r5, right=r2)
+ r = Relation.objects.create(left=r3, right=r2)
+
+ q1 = Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second'])
+ self.assertQuerysetEqual(q1, ["<Relation: First - Second>"])
+
+ q2 = Category.objects.filter(record__left_set__right__category__name='Second').order_by('name')
+ self.assertQuerysetEqual(q2, ["<Category: First>", "<Category: Second>"])
+
+ p = Parent.objects.create(name="Parent")
+ c = Child.objects.create(name="Child", parent=p)
+ self.assertRaises(ValueError, Child.objects.create, name="Grandchild", parent=c)
+
+ def test_fk_instantiation_outside_model(self):
+ # Regression for #12190 -- Should be able to instantiate a FK outside
+ # of a model, and interrogate its related field.
+ cat = models.ForeignKey(Category)
+ self.assertEqual('id', cat.rel.get_related_field().name)
diff --git a/parts/django/tests/regressiontests/max_lengths/__init__.py b/parts/django/tests/regressiontests/max_lengths/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/parts/django/tests/regressiontests/max_lengths/__init__.py
@@ -0,0 +1 @@
+
diff --git a/parts/django/tests/regressiontests/max_lengths/models.py b/parts/django/tests/regressiontests/max_lengths/models.py
new file mode 100644
index 0000000..78eb30c
--- /dev/null
+++ b/parts/django/tests/regressiontests/max_lengths/models.py
@@ -0,0 +1,13 @@
+from django.db import models
+
+class PersonWithDefaultMaxLengths(models.Model):
+ email = models.EmailField()
+ vcard = models.FileField(upload_to='/tmp')
+ homepage = models.URLField()
+ avatar = models.FilePathField()
+
+class PersonWithCustomMaxLengths(models.Model):
+ email = models.EmailField(max_length=250)
+ vcard = models.FileField(upload_to='/tmp', max_length=250)
+ homepage = models.URLField(max_length=250)
+ avatar = models.FilePathField(max_length=250)
diff --git a/parts/django/tests/regressiontests/max_lengths/tests.py b/parts/django/tests/regressiontests/max_lengths/tests.py
new file mode 100644
index 0000000..0fb2f30
--- /dev/null
+++ b/parts/django/tests/regressiontests/max_lengths/tests.py
@@ -0,0 +1,36 @@
+from unittest import TestCase
+from django.db import DatabaseError
+from regressiontests.max_lengths.models import PersonWithDefaultMaxLengths, PersonWithCustomMaxLengths
+
+class MaxLengthArgumentsTests(TestCase):
+
+ def verify_max_length(self, model,field,length):
+ self.assertEquals(model._meta.get_field(field).max_length,length)
+
+ def test_default_max_lengths(self):
+ self.verify_max_length(PersonWithDefaultMaxLengths, 'email', 75)
+ self.verify_max_length(PersonWithDefaultMaxLengths, 'vcard', 100)
+ self.verify_max_length(PersonWithDefaultMaxLengths, 'homepage', 200)
+ self.verify_max_length(PersonWithDefaultMaxLengths, 'avatar', 100)
+
+ def test_custom_max_lengths(self):
+ self.verify_max_length(PersonWithCustomMaxLengths, 'email', 250)
+ self.verify_max_length(PersonWithCustomMaxLengths, 'vcard', 250)
+ self.verify_max_length(PersonWithCustomMaxLengths, 'homepage', 250)
+ self.verify_max_length(PersonWithCustomMaxLengths, 'avatar', 250)
+
+class MaxLengthORMTests(TestCase):
+
+ def test_custom_max_lengths(self):
+ args = {
+ "email": "someone@example.com",
+ "vcard": "vcard",
+ "homepage": "http://example.com/",
+ "avatar": "me.jpg"
+ }
+
+ for field in ("email", "vcard", "homepage", "avatar"):
+ new_args = args.copy()
+ new_args[field] = "X" * 250 # a value longer than any of the default fields could hold.
+ p = PersonWithCustomMaxLengths.objects.create(**new_args)
+ self.assertEqual(getattr(p, field), ("X" * 250)) \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/middleware/__init__.py b/parts/django/tests/regressiontests/middleware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware/__init__.py
diff --git a/parts/django/tests/regressiontests/middleware/extra_urls.py b/parts/django/tests/regressiontests/middleware/extra_urls.py
new file mode 100644
index 0000000..b2a8902
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware/extra_urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('',
+ (r'^middleware/customurlconf/noslash$', 'view'),
+ (r'^middleware/customurlconf/slash/$', 'view'),
+ (r'^middleware/customurlconf/needsquoting#/$', 'view'),
+)
diff --git a/parts/django/tests/regressiontests/middleware/models.py b/parts/django/tests/regressiontests/middleware/models.py
new file mode 100644
index 0000000..71abcc5
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware/models.py
@@ -0,0 +1 @@
+# models.py file for tests to run.
diff --git a/parts/django/tests/regressiontests/middleware/tests.py b/parts/django/tests/regressiontests/middleware/tests.py
new file mode 100644
index 0000000..b77a2a3
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware/tests.py
@@ -0,0 +1,249 @@
+# -*- coding: utf-8 -*-
+
+from django.conf import settings
+from django.http import HttpRequest
+from django.middleware.common import CommonMiddleware
+from django.test import TestCase
+
+
+class CommonMiddlewareTest(TestCase):
+ def setUp(self):
+ self.slash = settings.APPEND_SLASH
+ self.www = settings.PREPEND_WWW
+
+ def tearDown(self):
+ settings.APPEND_SLASH = self.slash
+ settings.PREPEND_WWW = self.www
+
+ def _get_request(self, path):
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ }
+ request.path = request.path_info = "/middleware/%s" % path
+ return request
+
+ def test_append_slash_have_slash(self):
+ """
+ Tests that URLs with slashes go unmolested.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('slash/')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_slashless_resource(self):
+ """
+ Tests that matches to explicit slashless URLs go unmolested.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('noslash')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_slashless_unknown(self):
+ """
+ Tests that APPEND_SLASH doesn't redirect to unknown resources.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('unknown')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_redirect(self):
+ """
+ Tests that APPEND_SLASH redirects slashless URLs to a valid pattern.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('slash')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'], 'http://testserver/middleware/slash/')
+
+ def test_append_slash_no_redirect_on_POST_in_DEBUG(self):
+ """
+ Tests that while in debug mode, an exception is raised with a warning
+ when a failed attempt is made to POST to an URL which would normally be
+ redirected to a slashed version.
+ """
+ settings.APPEND_SLASH = True
+ settings.DEBUG = True
+ request = self._get_request('slash')
+ request.method = 'POST'
+ self.assertRaises(
+ RuntimeError,
+ CommonMiddleware().process_request,
+ request)
+ try:
+ CommonMiddleware().process_request(request)
+ except RuntimeError, e:
+ self.assertTrue('end in a slash' in str(e))
+ settings.DEBUG = False
+
+ def test_append_slash_disabled(self):
+ """
+ Tests disabling append slash functionality.
+ """
+ settings.APPEND_SLASH = False
+ request = self._get_request('slash')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_quoted(self):
+ """
+ Tests that URLs which require quoting are redirected to their slash
+ version ok.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('needsquoting#')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(
+ r['Location'],
+ 'http://testserver/middleware/needsquoting%23/')
+
+ def test_prepend_www(self):
+ settings.PREPEND_WWW = True
+ settings.APPEND_SLASH = False
+ request = self._get_request('path/')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(
+ r['Location'],
+ 'http://www.testserver/middleware/path/')
+
+ def test_prepend_www_append_slash_have_slash(self):
+ settings.PREPEND_WWW = True
+ settings.APPEND_SLASH = True
+ request = self._get_request('slash/')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'],
+ 'http://www.testserver/middleware/slash/')
+
+ def test_prepend_www_append_slash_slashless(self):
+ settings.PREPEND_WWW = True
+ settings.APPEND_SLASH = True
+ request = self._get_request('slash')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'],
+ 'http://www.testserver/middleware/slash/')
+
+
+ # The following tests examine expected behavior given a custom urlconf that
+ # overrides the default one through the request object.
+
+ def test_append_slash_have_slash_custom_urlconf(self):
+ """
+ Tests that URLs with slashes go unmolested.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/slash/')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_slashless_resource_custom_urlconf(self):
+ """
+ Tests that matches to explicit slashless URLs go unmolested.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/noslash')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_slashless_unknown_custom_urlconf(self):
+ """
+ Tests that APPEND_SLASH doesn't redirect to unknown resources.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/unknown')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_redirect_custom_urlconf(self):
+ """
+ Tests that APPEND_SLASH redirects slashless URLs to a valid pattern.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/slash')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ r = CommonMiddleware().process_request(request)
+ self.assertFalse(r is None,
+ "CommonMiddlware failed to return APPEND_SLASH redirect using request.urlconf")
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'], 'http://testserver/middleware/customurlconf/slash/')
+
+ def test_append_slash_no_redirect_on_POST_in_DEBUG_custom_urlconf(self):
+ """
+ Tests that while in debug mode, an exception is raised with a warning
+ when a failed attempt is made to POST to an URL which would normally be
+ redirected to a slashed version.
+ """
+ settings.APPEND_SLASH = True
+ settings.DEBUG = True
+ request = self._get_request('customurlconf/slash')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ request.method = 'POST'
+ self.assertRaises(
+ RuntimeError,
+ CommonMiddleware().process_request,
+ request)
+ try:
+ CommonMiddleware().process_request(request)
+ except RuntimeError, e:
+ self.assertTrue('end in a slash' in str(e))
+ settings.DEBUG = False
+
+ def test_append_slash_disabled_custom_urlconf(self):
+ """
+ Tests disabling append slash functionality.
+ """
+ settings.APPEND_SLASH = False
+ request = self._get_request('customurlconf/slash')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_quoted_custom_urlconf(self):
+ """
+ Tests that URLs which require quoting are redirected to their slash
+ version ok.
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/needsquoting#')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ r = CommonMiddleware().process_request(request)
+ self.assertFalse(r is None,
+ "CommonMiddlware failed to return APPEND_SLASH redirect using request.urlconf")
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(
+ r['Location'],
+ 'http://testserver/middleware/customurlconf/needsquoting%23/')
+
+ def test_prepend_www_custom_urlconf(self):
+ settings.PREPEND_WWW = True
+ settings.APPEND_SLASH = False
+ request = self._get_request('customurlconf/path/')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(
+ r['Location'],
+ 'http://www.testserver/middleware/customurlconf/path/')
+
+ def test_prepend_www_append_slash_have_slash_custom_urlconf(self):
+ settings.PREPEND_WWW = True
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/slash/')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'],
+ 'http://www.testserver/middleware/customurlconf/slash/')
+
+ def test_prepend_www_append_slash_slashless_custom_urlconf(self):
+ settings.PREPEND_WWW = True
+ settings.APPEND_SLASH = True
+ request = self._get_request('customurlconf/slash')
+ request.urlconf = 'regressiontests.middleware.extra_urls'
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'],
+ 'http://www.testserver/middleware/customurlconf/slash/')
diff --git a/parts/django/tests/regressiontests/middleware/urls.py b/parts/django/tests/regressiontests/middleware/urls.py
new file mode 100644
index 0000000..88a4b37
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('',
+ (r'^noslash$', 'view'),
+ (r'^slash/$', 'view'),
+ (r'^needsquoting#/$', 'view'),
+)
diff --git a/parts/django/tests/regressiontests/middleware_exceptions/__init__.py b/parts/django/tests/regressiontests/middleware_exceptions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware_exceptions/__init__.py
diff --git a/parts/django/tests/regressiontests/middleware_exceptions/models.py b/parts/django/tests/regressiontests/middleware_exceptions/models.py
new file mode 100644
index 0000000..137941f
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware_exceptions/models.py
@@ -0,0 +1 @@
+from django.db import models
diff --git a/parts/django/tests/regressiontests/middleware_exceptions/tests.py b/parts/django/tests/regressiontests/middleware_exceptions/tests.py
new file mode 100644
index 0000000..3d9c5f6
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware_exceptions/tests.py
@@ -0,0 +1,40 @@
+import sys
+
+from django.test import TestCase
+from django.core.signals import got_request_exception
+
+class TestException(Exception):
+ pass
+
+class TestMiddleware(object):
+ def process_request(self, request):
+ raise TestException('Test Exception')
+
+class MiddlewareExceptionTest(TestCase):
+ def setUp(self):
+ self.exceptions = []
+ got_request_exception.connect(self._on_request_exception)
+ self.client.handler.load_middleware()
+
+ def tearDown(self):
+ got_request_exception.disconnect(self._on_request_exception)
+ self.exceptions = []
+
+ def _on_request_exception(self, sender, request, **kwargs):
+ self.exceptions.append(sys.exc_info())
+
+ def test_process_request(self):
+ self.client.handler._request_middleware.insert(0, TestMiddleware().process_request)
+ try:
+ response = self.client.get('/')
+ except TestException, e:
+ # Test client indefinitely re-raises any exceptions being raised
+ # during request handling. Hence actual testing that exception was
+ # properly handled is done by relying on got_request_exception
+ # signal being sent.
+ pass
+ except Exception, e:
+ self.fail("Unexpected exception: %s" % e)
+ self.assertEquals(len(self.exceptions), 1)
+ exception, value, tb = self.exceptions[0]
+ self.assertEquals(value.args, ('Test Exception', ))
diff --git a/parts/django/tests/regressiontests/middleware_exceptions/urls.py b/parts/django/tests/regressiontests/middleware_exceptions/urls.py
new file mode 100644
index 0000000..63f3ba1
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware_exceptions/urls.py
@@ -0,0 +1,8 @@
+# coding: utf-8
+from django.conf.urls.defaults import *
+
+import views
+
+urlpatterns = patterns('',
+ (r'^$', views.index),
+)
diff --git a/parts/django/tests/regressiontests/middleware_exceptions/views.py b/parts/django/tests/regressiontests/middleware_exceptions/views.py
new file mode 100644
index 0000000..e2d597d
--- /dev/null
+++ b/parts/django/tests/regressiontests/middleware_exceptions/views.py
@@ -0,0 +1,4 @@
+from django import http
+
+def index(request):
+ return http.HttpResponse('')
diff --git a/parts/django/tests/regressiontests/model_fields/4x8.png b/parts/django/tests/regressiontests/model_fields/4x8.png
new file mode 100644
index 0000000..ffce444
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_fields/4x8.png
Binary files differ
diff --git a/parts/django/tests/regressiontests/model_fields/8x4.png b/parts/django/tests/regressiontests/model_fields/8x4.png
new file mode 100644
index 0000000..60e6d69
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_fields/8x4.png
Binary files differ
diff --git a/parts/django/tests/regressiontests/model_fields/__init__.py b/parts/django/tests/regressiontests/model_fields/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_fields/__init__.py
diff --git a/parts/django/tests/regressiontests/model_fields/imagefield.py b/parts/django/tests/regressiontests/model_fields/imagefield.py
new file mode 100644
index 0000000..dd79e7a
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_fields/imagefield.py
@@ -0,0 +1,420 @@
+import os
+import shutil
+
+from django.core.files import File
+from django.core.files.base import ContentFile
+from django.core.files.images import ImageFile
+from django.test import TestCase
+
+from models import Image, Person, PersonWithHeight, PersonWithHeightAndWidth, \
+ PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile
+
+
+# If PIL available, do these tests.
+if Image:
+
+ from models import temp_storage_dir
+
+
+ class ImageFieldTestMixin(object):
+ """
+ Mixin class to provide common functionality to ImageField test classes.
+ """
+
+ # Person model to use for tests.
+ PersonModel = PersonWithHeightAndWidth
+ # File class to use for file instances.
+ File = ImageFile
+
+ def setUp(self):
+ """
+ Creates a pristine temp directory (or deletes and recreates if it
+ already exists) that the model uses as its storage directory.
+
+ Sets up two ImageFile instances for use in tests.
+ """
+ if os.path.exists(temp_storage_dir):
+ shutil.rmtree(temp_storage_dir)
+ os.mkdir(temp_storage_dir)
+
+ file_path1 = os.path.join(os.path.dirname(__file__), "4x8.png")
+ self.file1 = self.File(open(file_path1, 'rb'))
+
+ file_path2 = os.path.join(os.path.dirname(__file__), "8x4.png")
+ self.file2 = self.File(open(file_path2, 'rb'))
+
+ def tearDown(self):
+ """
+ Removes temp directory and all its contents.
+ """
+ shutil.rmtree(temp_storage_dir)
+
+ def check_dimensions(self, instance, width, height,
+ field_name='mugshot'):
+ """
+ Asserts that the given width and height values match both the
+ field's height and width attributes and the height and width fields
+ (if defined) the image field is caching to.
+
+ Note, this method will check for dimension fields named by adding
+ "_width" or "_height" to the name of the ImageField. So, the
+ models used in these tests must have their fields named
+ accordingly.
+
+ By default, we check the field named "mugshot", but this can be
+ specified by passing the field_name parameter.
+ """
+ field = getattr(instance, field_name)
+ # Check height/width attributes of field.
+ if width is None and height is None:
+ self.assertRaises(ValueError, getattr, field, 'width')
+ self.assertRaises(ValueError, getattr, field, 'height')
+ else:
+ self.assertEqual(field.width, width)
+ self.assertEqual(field.height, height)
+
+ # Check height/width fields of model, if defined.
+ width_field_name = field_name + '_width'
+ if hasattr(instance, width_field_name):
+ self.assertEqual(getattr(instance, width_field_name), width)
+ height_field_name = field_name + '_height'
+ if hasattr(instance, height_field_name):
+ self.assertEqual(getattr(instance, height_field_name), height)
+
+
+ class ImageFieldTests(ImageFieldTestMixin, TestCase):
+ """
+ Tests for ImageField that don't need to be run with each of the
+ different test model classes.
+ """
+
+ def test_equal_notequal_hash(self):
+ """
+ Bug #9786: Ensure '==' and '!=' work correctly.
+ Bug #9508: make sure hash() works as expected (equal items must
+ hash to the same value).
+ """
+ # Create two Persons with different mugshots.
+ p1 = self.PersonModel(name="Joe")
+ p1.mugshot.save("mug", self.file1)
+ p2 = self.PersonModel(name="Bob")
+ p2.mugshot.save("mug", self.file2)
+ self.assertEqual(p1.mugshot == p2.mugshot, False)
+ self.assertEqual(p1.mugshot != p2.mugshot, True)
+
+ # Test again with an instance fetched from the db.
+ p1_db = self.PersonModel.objects.get(name="Joe")
+ self.assertEqual(p1_db.mugshot == p2.mugshot, False)
+ self.assertEqual(p1_db.mugshot != p2.mugshot, True)
+
+ # Instance from db should match the local instance.
+ self.assertEqual(p1_db.mugshot == p1.mugshot, True)
+ self.assertEqual(hash(p1_db.mugshot), hash(p1.mugshot))
+ self.assertEqual(p1_db.mugshot != p1.mugshot, False)
+
+ def test_instantiate_missing(self):
+ """
+ If the underlying file is unavailable, still create instantiate the
+ object without error.
+ """
+ p = self.PersonModel(name="Joan")
+ p.mugshot.save("shot", self.file1)
+ p = self.PersonModel.objects.get(name="Joan")
+ path = p.mugshot.path
+ shutil.move(path, path + '.moved')
+ p2 = self.PersonModel.objects.get(name="Joan")
+
+ def test_delete_when_missing(self):
+ """
+ Bug #8175: correctly delete an object where the file no longer
+ exists on the file system.
+ """
+ p = self.PersonModel(name="Fred")
+ p.mugshot.save("shot", self.file1)
+ os.remove(p.mugshot.path)
+ p.delete()
+
+ def test_size_method(self):
+ """
+ Bug #8534: FileField.size should not leave the file open.
+ """
+ p = self.PersonModel(name="Joan")
+ p.mugshot.save("shot", self.file1)
+
+ # Get a "clean" model instance
+ p = self.PersonModel.objects.get(name="Joan")
+ # It won't have an opened file.
+ self.assertEqual(p.mugshot.closed, True)
+
+ # After asking for the size, the file should still be closed.
+ _ = p.mugshot.size
+ self.assertEqual(p.mugshot.closed, True)
+
+ def test_pickle(self):
+ """
+ Tests that ImageField can be pickled, unpickled, and that the
+ image of the unpickled version is the same as the original.
+ """
+ import pickle
+
+ p = Person(name="Joe")
+ p.mugshot.save("mug", self.file1)
+ dump = pickle.dumps(p)
+
+ p2 = Person(name="Bob")
+ p2.mugshot = self.file1
+
+ loaded_p = pickle.loads(dump)
+ self.assertEqual(p.mugshot, loaded_p.mugshot)
+
+
+ class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase):
+ """
+ Tests behavior of an ImageField and its dimensions fields.
+ """
+
+ def test_constructor(self):
+ """
+ Tests assigning an image field through the model's constructor.
+ """
+ p = self.PersonModel(name='Joe', mugshot=self.file1)
+ self.check_dimensions(p, 4, 8)
+ p.save()
+ self.check_dimensions(p, 4, 8)
+
+ def test_image_after_constructor(self):
+ """
+ Tests behavior when image is not passed in constructor.
+ """
+ p = self.PersonModel(name='Joe')
+ # TestImageField value will default to being an instance of its
+ # attr_class, a TestImageFieldFile, with name == None, which will
+ # cause it to evaluate as False.
+ self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True)
+ self.assertEqual(bool(p.mugshot), False)
+
+ # Test setting a fresh created model instance.
+ p = self.PersonModel(name='Joe')
+ p.mugshot = self.file1
+ self.check_dimensions(p, 4, 8)
+
+ def test_create(self):
+ """
+ Tests assigning an image in Manager.create().
+ """
+ p = self.PersonModel.objects.create(name='Joe', mugshot=self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ def test_default_value(self):
+ """
+ Tests that the default value for an ImageField is an instance of
+ the field's attr_class (TestImageFieldFile in this case) with no
+ name (name set to None).
+ """
+ p = self.PersonModel()
+ self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True)
+ self.assertEqual(bool(p.mugshot), False)
+
+ def test_assignment_to_None(self):
+ """
+ Tests that assigning ImageField to None clears dimensions.
+ """
+ p = self.PersonModel(name='Joe', mugshot=self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ # If image assigned to None, dimension fields should be cleared.
+ p.mugshot = None
+ self.check_dimensions(p, None, None)
+
+ p.mugshot = self.file2
+ self.check_dimensions(p, 8, 4)
+
+ def test_field_save_and_delete_methods(self):
+ """
+ Tests assignment using the field's save method and deletion using
+ the field's delete method.
+ """
+ p = self.PersonModel(name='Joe')
+ p.mugshot.save("mug", self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ # A new file should update dimensions.
+ p.mugshot.save("mug", self.file2)
+ self.check_dimensions(p, 8, 4)
+
+ # Field and dimensions should be cleared after a delete.
+ p.mugshot.delete(save=False)
+ self.assertEqual(p.mugshot, None)
+ self.check_dimensions(p, None, None)
+
+ def test_dimensions(self):
+ """
+ Checks that dimensions are updated correctly in various situations.
+ """
+ p = self.PersonModel(name='Joe')
+
+ # Dimensions should get set if file is saved.
+ p.mugshot.save("mug", self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ # Test dimensions after fetching from database.
+ p = self.PersonModel.objects.get(name='Joe')
+ # Bug 11084: Dimensions should not get recalculated if file is
+ # coming from the database. We test this by checking if the file
+ # was opened.
+ self.assertEqual(p.mugshot.was_opened, False)
+ self.check_dimensions(p, 4, 8)
+ # After checking dimensions on the image field, the file will have
+ # opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+ # Dimensions should now be cached, and if we reset was_opened and
+ # check dimensions again, the file should not have opened.
+ p.mugshot.was_opened = False
+ self.check_dimensions(p, 4, 8)
+ self.assertEqual(p.mugshot.was_opened, False)
+
+ # If we assign a new image to the instance, the dimensions should
+ # update.
+ p.mugshot = self.file2
+ self.check_dimensions(p, 8, 4)
+ # Dimensions were recalculated, and hence file should have opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+
+
+ class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField with no dimension fields.
+ """
+
+ PersonModel = Person
+
+
+ class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField with one dimensions field.
+ """
+
+ PersonModel = PersonWithHeight
+
+
+ class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField where the dimensions fields are
+ defined before the ImageField.
+ """
+
+ PersonModel = PersonDimensionsFirst
+
+
+ class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField when assigning it a File instance
+ rather than an ImageFile instance.
+ """
+
+ PersonModel = PersonDimensionsFirst
+ File = File
+
+
+ class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
+ """
+ Tests a model with two ImageFields.
+ """
+
+ PersonModel = PersonTwoImages
+
+ def test_constructor(self):
+ p = self.PersonModel(mugshot=self.file1, headshot=self.file2)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ p.save()
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ def test_create(self):
+ p = self.PersonModel.objects.create(mugshot=self.file1,
+ headshot=self.file2)
+ self.check_dimensions(p, 4, 8)
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ def test_assignment(self):
+ p = self.PersonModel()
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+
+ p.mugshot = self.file1
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+ p.headshot = self.file2
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ # Clear the ImageFields one at a time.
+ p.mugshot = None
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ p.headshot = None
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+
+ def test_field_save_and_delete_methods(self):
+ p = self.PersonModel(name='Joe')
+ p.mugshot.save("mug", self.file1)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+ p.headshot.save("head", self.file2)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ # We can use save=True when deleting the image field with null=True
+ # dimension fields and the other field has an image.
+ p.headshot.delete(save=True)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+ p.mugshot.delete(save=False)
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+
+ def test_dimensions(self):
+ """
+ Checks that dimensions are updated correctly in various situations.
+ """
+ p = self.PersonModel(name='Joe')
+
+ # Dimensions should get set for the saved file.
+ p.mugshot.save("mug", self.file1)
+ p.headshot.save("head", self.file2)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ # Test dimensions after fetching from database.
+ p = self.PersonModel.objects.get(name='Joe')
+ # Bug 11084: Dimensions should not get recalculated if file is
+ # coming from the database. We test this by checking if the file
+ # was opened.
+ self.assertEqual(p.mugshot.was_opened, False)
+ self.assertEqual(p.headshot.was_opened, False)
+ self.check_dimensions(p, 4, 8,'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ # After checking dimensions on the image fields, the files will
+ # have been opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+ self.assertEqual(p.headshot.was_opened, True)
+ # Dimensions should now be cached, and if we reset was_opened and
+ # check dimensions again, the file should not have opened.
+ p.mugshot.was_opened = False
+ p.headshot.was_opened = False
+ self.check_dimensions(p, 4, 8,'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ self.assertEqual(p.mugshot.was_opened, False)
+ self.assertEqual(p.headshot.was_opened, False)
+
+ # If we assign a new image to the instance, the dimensions should
+ # update.
+ p.mugshot = self.file2
+ p.headshot = self.file1
+ self.check_dimensions(p, 8, 4, 'mugshot')
+ self.check_dimensions(p, 4, 8, 'headshot')
+ # Dimensions were recalculated, and hence file should have opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+ self.assertEqual(p.headshot.was_opened, True)
diff --git a/parts/django/tests/regressiontests/model_fields/models.py b/parts/django/tests/regressiontests/model_fields/models.py
new file mode 100644
index 0000000..45cd223
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_fields/models.py
@@ -0,0 +1,154 @@
+import os
+import tempfile
+
+# Try to import PIL in either of the two ways it can end up installed.
+# Checking for the existence of Image is enough for CPython, but for PyPy,
+# you need to check for the underlying modules.
+
+try:
+ from PIL import Image, _imaging
+except ImportError:
+ try:
+ import Image, _imaging
+ except ImportError:
+ Image = None
+
+from django.core.files.storage import FileSystemStorage
+from django.db import models
+from django.db.models.fields.files import ImageFieldFile, ImageField
+
+
+class Foo(models.Model):
+ a = models.CharField(max_length=10)
+ d = models.DecimalField(max_digits=5, decimal_places=3)
+
+def get_foo():
+ return Foo.objects.get(id=1)
+
+class Bar(models.Model):
+ b = models.CharField(max_length=10)
+ a = models.ForeignKey(Foo, default=get_foo)
+
+class Whiz(models.Model):
+ CHOICES = (
+ ('Group 1', (
+ (1,'First'),
+ (2,'Second'),
+ )
+ ),
+ ('Group 2', (
+ (3,'Third'),
+ (4,'Fourth'),
+ )
+ ),
+ (0,'Other'),
+ )
+ c = models.IntegerField(choices=CHOICES, null=True)
+
+class BigD(models.Model):
+ d = models.DecimalField(max_digits=38, decimal_places=30)
+
+class BigS(models.Model):
+ s = models.SlugField(max_length=255)
+
+class BigInt(models.Model):
+ value = models.BigIntegerField()
+ null_value = models.BigIntegerField(null = True, blank = True)
+
+class Post(models.Model):
+ title = models.CharField(max_length=100)
+ body = models.TextField()
+
+class NullBooleanModel(models.Model):
+ nbfield = models.NullBooleanField()
+
+class BooleanModel(models.Model):
+ bfield = models.BooleanField()
+ string = models.CharField(max_length=10, default='abc')
+
+###############################################################################
+# ImageField
+
+# If PIL available, do these tests.
+if Image:
+ class TestImageFieldFile(ImageFieldFile):
+ """
+ Custom Field File class that records whether or not the underlying file
+ was opened.
+ """
+ def __init__(self, *args, **kwargs):
+ self.was_opened = False
+ super(TestImageFieldFile, self).__init__(*args,**kwargs)
+ def open(self):
+ self.was_opened = True
+ super(TestImageFieldFile, self).open()
+
+ class TestImageField(ImageField):
+ attr_class = TestImageFieldFile
+
+ # Set up a temp directory for file storage.
+ temp_storage_dir = tempfile.mkdtemp()
+ temp_storage = FileSystemStorage(temp_storage_dir)
+ temp_upload_to_dir = os.path.join(temp_storage.location, 'tests')
+
+ class Person(models.Model):
+ """
+ Model that defines an ImageField with no dimension fields.
+ """
+ name = models.CharField(max_length=50)
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests')
+
+ class PersonWithHeight(models.Model):
+ """
+ Model that defines an ImageField with only one dimension field.
+ """
+ name = models.CharField(max_length=50)
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height')
+ mugshot_height = models.PositiveSmallIntegerField()
+
+ class PersonWithHeightAndWidth(models.Model):
+ """
+ Model that defines height and width fields after the ImageField.
+ """
+ name = models.CharField(max_length=50)
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height',
+ width_field='mugshot_width')
+ mugshot_height = models.PositiveSmallIntegerField()
+ mugshot_width = models.PositiveSmallIntegerField()
+
+ class PersonDimensionsFirst(models.Model):
+ """
+ Model that defines height and width fields before the ImageField.
+ """
+ name = models.CharField(max_length=50)
+ mugshot_height = models.PositiveSmallIntegerField()
+ mugshot_width = models.PositiveSmallIntegerField()
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height',
+ width_field='mugshot_width')
+
+ class PersonTwoImages(models.Model):
+ """
+ Model that:
+ * Defines two ImageFields
+ * Defines the height/width fields before the ImageFields
+ * Has a nullalble ImageField
+ """
+ name = models.CharField(max_length=50)
+ mugshot_height = models.PositiveSmallIntegerField()
+ mugshot_width = models.PositiveSmallIntegerField()
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height',
+ width_field='mugshot_width')
+ headshot_height = models.PositiveSmallIntegerField(
+ blank=True, null=True)
+ headshot_width = models.PositiveSmallIntegerField(
+ blank=True, null=True)
+ headshot = TestImageField(blank=True, null=True,
+ storage=temp_storage, upload_to='tests',
+ height_field='headshot_height',
+ width_field='headshot_width')
+
+###############################################################################
diff --git a/parts/django/tests/regressiontests/model_fields/tests.py b/parts/django/tests/regressiontests/model_fields/tests.py
new file mode 100644
index 0000000..72a7d4d
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_fields/tests.py
@@ -0,0 +1,313 @@
+import datetime
+import unittest
+from decimal import Decimal
+
+import django.test
+from django import forms
+from django.db import models
+from django.core.exceptions import ValidationError
+
+from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel
+
+# If PIL available, do these tests.
+if Image:
+ from imagefield import \
+ ImageFieldTests, \
+ ImageFieldTwoDimensionsTests, \
+ ImageFieldNoDimensionsTests, \
+ ImageFieldOneDimensionTests, \
+ ImageFieldDimensionsFirstTests, \
+ ImageFieldUsingFileTests, \
+ TwoImageFieldTests
+
+
+class BasicFieldTests(django.test.TestCase):
+ def test_show_hidden_initial(self):
+ """
+ Regression test for #12913. Make sure fields with choices respect
+ show_hidden_initial as a kwarg to models.Field.formfield()
+ """
+ choices = [(0, 0), (1, 1)]
+ model_field = models.Field(choices=choices)
+ form_field = model_field.formfield(show_hidden_initial=True)
+ self.assertTrue(form_field.show_hidden_initial)
+
+ form_field = model_field.formfield(show_hidden_initial=False)
+ self.assertFalse(form_field.show_hidden_initial)
+
+ def test_nullbooleanfield_blank(self):
+ """
+ Regression test for #13071: NullBooleanField should not throw
+ a validation error when given a value of None.
+
+ """
+ nullboolean = NullBooleanModel(nbfield=None)
+ try:
+ nullboolean.full_clean()
+ except ValidationError, e:
+ self.fail("NullBooleanField failed validation with value of None: %s" % e.messages)
+
+class DecimalFieldTests(django.test.TestCase):
+ def test_to_python(self):
+ f = models.DecimalField(max_digits=4, decimal_places=2)
+ self.assertEqual(f.to_python(3), Decimal("3"))
+ self.assertEqual(f.to_python("3.14"), Decimal("3.14"))
+ self.assertRaises(ValidationError, f.to_python, "abc")
+
+ def test_default(self):
+ f = models.DecimalField(default=Decimal("0.00"))
+ self.assertEqual(f.get_default(), Decimal("0.00"))
+
+ def test_format(self):
+ f = models.DecimalField(max_digits=5, decimal_places=1)
+ self.assertEqual(f._format(f.to_python(2)), u'2.0')
+ self.assertEqual(f._format(f.to_python('2.6')), u'2.6')
+ self.assertEqual(f._format(None), None)
+
+ def test_get_db_prep_lookup(self):
+ from django.db import connection
+ f = models.DecimalField(max_digits=5, decimal_places=1)
+ self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None])
+
+ def test_filter_with_strings(self):
+ """
+ We should be able to filter decimal fields using strings (#8023)
+ """
+ Foo.objects.create(id=1, a='abc', d=Decimal("12.34"))
+ self.assertEqual(list(Foo.objects.filter(d=u'1.23')), [])
+
+ def test_save_without_float_conversion(self):
+ """
+ Ensure decimals don't go through a corrupting float conversion during
+ save (#5079).
+ """
+ bd = BigD(d="12.9")
+ bd.save()
+ bd = BigD.objects.get(pk=bd.pk)
+ self.assertEqual(bd.d, Decimal("12.9"))
+
+ def test_lookup_really_big_value(self):
+ """
+ Ensure that really big values can be used in a filter statement, even
+ with older Python versions.
+ """
+ # This should not crash. That counts as a win for our purposes.
+ Foo.objects.filter(d__gte=100000000000)
+
+class ForeignKeyTests(django.test.TestCase):
+ def test_callable_default(self):
+ """Test the use of a lazy callable for ForeignKey.default"""
+ a = Foo.objects.create(id=1, a='abc', d=Decimal("12.34"))
+ b = Bar.objects.create(b="bcd")
+ self.assertEqual(b.a, a)
+
+class DateTimeFieldTests(unittest.TestCase):
+ def test_datetimefield_to_python_usecs(self):
+ """DateTimeField.to_python should support usecs"""
+ f = models.DateTimeField()
+ self.assertEqual(f.to_python('2001-01-02 03:04:05.000006'),
+ datetime.datetime(2001, 1, 2, 3, 4, 5, 6))
+ self.assertEqual(f.to_python('2001-01-02 03:04:05.999999'),
+ datetime.datetime(2001, 1, 2, 3, 4, 5, 999999))
+
+ def test_timefield_to_python_usecs(self):
+ """TimeField.to_python should support usecs"""
+ f = models.TimeField()
+ self.assertEqual(f.to_python('01:02:03.000004'),
+ datetime.time(1, 2, 3, 4))
+ self.assertEqual(f.to_python('01:02:03.999999'),
+ datetime.time(1, 2, 3, 999999))
+
+class BooleanFieldTests(unittest.TestCase):
+ def _test_get_db_prep_lookup(self, f):
+ from django.db import connection
+ self.assertEqual(f.get_db_prep_lookup('exact', True, connection=connection), [True])
+ self.assertEqual(f.get_db_prep_lookup('exact', '1', connection=connection), [True])
+ self.assertEqual(f.get_db_prep_lookup('exact', 1, connection=connection), [True])
+ self.assertEqual(f.get_db_prep_lookup('exact', False, connection=connection), [False])
+ self.assertEqual(f.get_db_prep_lookup('exact', '0', connection=connection), [False])
+ self.assertEqual(f.get_db_prep_lookup('exact', 0, connection=connection), [False])
+ self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None])
+
+ def _test_to_python(self, f):
+ self.assertTrue(f.to_python(1) is True)
+ self.assertTrue(f.to_python(0) is False)
+
+ def test_booleanfield_get_db_prep_lookup(self):
+ self._test_get_db_prep_lookup(models.BooleanField())
+
+ def test_nullbooleanfield_get_db_prep_lookup(self):
+ self._test_get_db_prep_lookup(models.NullBooleanField())
+
+ def test_booleanfield_to_python(self):
+ self._test_to_python(models.BooleanField())
+
+ def test_nullbooleanfield_to_python(self):
+ self._test_to_python(models.NullBooleanField())
+
+ def test_booleanfield_choices_blank(self):
+ """
+ Test that BooleanField with choices and defaults doesn't generate a
+ formfield with the blank option (#9640, #10549).
+ """
+ choices = [(1, u'Si'), (2, 'No')]
+ f = models.BooleanField(choices=choices, default=1, null=True)
+ self.assertEqual(f.formfield().choices, [('', '---------')] + choices)
+
+ f = models.BooleanField(choices=choices, default=1, null=False)
+ self.assertEqual(f.formfield().choices, choices)
+
+ def test_return_type(self):
+ b = BooleanModel()
+ b.bfield = True
+ b.save()
+ b2 = BooleanModel.objects.get(pk=b.pk)
+ self.assertTrue(isinstance(b2.bfield, bool))
+ self.assertEqual(b2.bfield, True)
+
+ b3 = BooleanModel()
+ b3.bfield = False
+ b3.save()
+ b4 = BooleanModel.objects.get(pk=b3.pk)
+ self.assertTrue(isinstance(b4.bfield, bool))
+ self.assertEqual(b4.bfield, False)
+
+ b = NullBooleanModel()
+ b.nbfield = True
+ b.save()
+ b2 = NullBooleanModel.objects.get(pk=b.pk)
+ self.assertTrue(isinstance(b2.nbfield, bool))
+ self.assertEqual(b2.nbfield, True)
+
+ b3 = NullBooleanModel()
+ b3.nbfield = False
+ b3.save()
+ b4 = NullBooleanModel.objects.get(pk=b3.pk)
+ self.assertTrue(isinstance(b4.nbfield, bool))
+ self.assertEqual(b4.nbfield, False)
+
+ # http://code.djangoproject.com/ticket/13293
+ # Verify that when an extra clause exists, the boolean
+ # conversions are applied with an offset
+ b5 = BooleanModel.objects.all().extra(
+ select={'string_length': 'LENGTH(string)'})[0]
+ self.assertFalse(isinstance(b5.pk, bool))
+
+class ChoicesTests(django.test.TestCase):
+ def test_choices_and_field_display(self):
+ """
+ Check that get_choices and get_flatchoices interact with
+ get_FIELD_display to return the expected values (#7913).
+ """
+ self.assertEqual(Whiz(c=1).get_c_display(), 'First') # A nested value
+ self.assertEqual(Whiz(c=0).get_c_display(), 'Other') # A top level value
+ self.assertEqual(Whiz(c=9).get_c_display(), 9) # Invalid value
+ self.assertEqual(Whiz(c=None).get_c_display(), None) # Blank value
+ self.assertEqual(Whiz(c='').get_c_display(), '') # Empty value
+
+class SlugFieldTests(django.test.TestCase):
+ def test_slugfield_max_length(self):
+ """
+ Make sure SlugField honors max_length (#9706)
+ """
+ bs = BigS.objects.create(s = 'slug'*50)
+ bs = BigS.objects.get(pk=bs.pk)
+ self.assertEqual(bs.s, 'slug'*50)
+
+
+class ValidationTest(django.test.TestCase):
+ def test_charfield_raises_error_on_empty_string(self):
+ f = models.CharField()
+ self.assertRaises(ValidationError, f.clean, "", None)
+
+ def test_charfield_cleans_empty_string_when_blank_true(self):
+ f = models.CharField(blank=True)
+ self.assertEqual('', f.clean('', None))
+
+ def test_integerfield_cleans_valid_string(self):
+ f = models.IntegerField()
+ self.assertEqual(2, f.clean('2', None))
+
+ def test_integerfield_raises_error_on_invalid_intput(self):
+ f = models.IntegerField()
+ self.assertRaises(ValidationError, f.clean, "a", None)
+
+ def test_charfield_with_choices_cleans_valid_choice(self):
+ f = models.CharField(max_length=1, choices=[('a','A'), ('b','B')])
+ self.assertEqual('a', f.clean('a', None))
+
+ def test_charfield_with_choices_raises_error_on_invalid_choice(self):
+ f = models.CharField(choices=[('a','A'), ('b','B')])
+ self.assertRaises(ValidationError, f.clean, "not a", None)
+
+ def test_choices_validation_supports_named_groups(self):
+ f = models.IntegerField(choices=(('group',((10,'A'),(20,'B'))),(30,'C')))
+ self.assertEqual(10, f.clean(10, None))
+
+ def test_nullable_integerfield_raises_error_with_blank_false(self):
+ f = models.IntegerField(null=True, blank=False)
+ self.assertRaises(ValidationError, f.clean, None, None)
+
+ def test_nullable_integerfield_cleans_none_on_null_and_blank_true(self):
+ f = models.IntegerField(null=True, blank=True)
+ self.assertEqual(None, f.clean(None, None))
+
+ def test_integerfield_raises_error_on_empty_input(self):
+ f = models.IntegerField(null=False)
+ self.assertRaises(ValidationError, f.clean, None, None)
+ self.assertRaises(ValidationError, f.clean, '', None)
+
+ def test_charfield_raises_error_on_empty_input(self):
+ f = models.CharField(null=False)
+ self.assertRaises(ValidationError, f.clean, None, None)
+
+ def test_datefield_cleans_date(self):
+ f = models.DateField()
+ self.assertEqual(datetime.date(2008, 10, 10), f.clean('2008-10-10', None))
+
+ def test_boolean_field_doesnt_accept_empty_input(self):
+ f = models.BooleanField()
+ self.assertRaises(ValidationError, f.clean, None, None)
+
+
+class BigIntegerFieldTests(django.test.TestCase):
+ def test_limits(self):
+ # Ensure that values that are right at the limits can be saved
+ # and then retrieved without corruption.
+ maxval = 9223372036854775807
+ minval = -maxval - 1
+ BigInt.objects.create(value=maxval)
+ qs = BigInt.objects.filter(value__gte=maxval)
+ self.assertEqual(qs.count(), 1)
+ self.assertEqual(qs[0].value, maxval)
+ BigInt.objects.create(value=minval)
+ qs = BigInt.objects.filter(value__lte=minval)
+ self.assertEqual(qs.count(), 1)
+ self.assertEqual(qs[0].value, minval)
+
+ def test_types(self):
+ b = BigInt(value = 0)
+ self.assertTrue(isinstance(b.value, (int, long)))
+ b.save()
+ self.assertTrue(isinstance(b.value, (int, long)))
+ b = BigInt.objects.all()[0]
+ self.assertTrue(isinstance(b.value, (int, long)))
+
+ def test_coercing(self):
+ BigInt.objects.create(value ='10')
+ b = BigInt.objects.get(value = '10')
+ self.assertEqual(b.value, 10)
+
+class TypeCoercionTests(django.test.TestCase):
+ """
+ Test that database lookups can accept the wrong types and convert
+ them with no error: especially on Postgres 8.3+ which does not do
+ automatic casting at the DB level. See #10015.
+
+ """
+ def test_lookup_integer_in_charfield(self):
+ self.assertEquals(Post.objects.filter(title=9).count(), 0)
+
+ def test_lookup_integer_in_textfield(self):
+ self.assertEquals(Post.objects.filter(body=24).count(), 0)
+
diff --git a/parts/django/tests/regressiontests/model_forms_regress/__init__.py b/parts/django/tests/regressiontests/model_forms_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_forms_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/model_forms_regress/models.py b/parts/django/tests/regressiontests/model_forms_regress/models.py
new file mode 100644
index 0000000..4f9811a
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_forms_regress/models.py
@@ -0,0 +1,59 @@
+import os
+from django.db import models
+from django.core.exceptions import ValidationError
+
+
+class Person(models.Model):
+ name = models.CharField(max_length=100)
+
+class Triple(models.Model):
+ left = models.IntegerField()
+ middle = models.IntegerField()
+ right = models.IntegerField()
+
+ class Meta:
+ unique_together = (('left', 'middle'), (u'middle', u'right'))
+
+class FilePathModel(models.Model):
+ path = models.FilePathField(path=os.path.dirname(__file__), match=".*\.py$", blank=True)
+
+class Publication(models.Model):
+ title = models.CharField(max_length=30)
+ date_published = models.DateField()
+
+ def __unicode__(self):
+ return self.title
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100)
+ publications = models.ManyToManyField(Publication)
+
+ def __unicode__(self):
+ return self.headline
+
+class CustomFileField(models.FileField):
+ def save_form_data(self, instance, data):
+ been_here = getattr(self, 'been_saved', False)
+ assert not been_here, "save_form_data called more than once"
+ setattr(self, 'been_saved', True)
+
+class CustomFF(models.Model):
+ f = CustomFileField(upload_to='unused', blank=True)
+
+class RealPerson(models.Model):
+ name = models.CharField(max_length=100)
+
+ def clean(self):
+ if self.name.lower() == 'anonymous':
+ raise ValidationError("Please specify a real name.")
+
+class Author(models.Model):
+ publication = models.OneToOneField(Publication, null=True, blank=True)
+ full_name = models.CharField(max_length=255)
+
+class Author1(models.Model):
+ publication = models.OneToOneField(Publication, null=False)
+ full_name = models.CharField(max_length=255)
+
+class Homepage(models.Model):
+ url = models.URLField(verify_exists=False)
diff --git a/parts/django/tests/regressiontests/model_forms_regress/tests.py b/parts/django/tests/regressiontests/model_forms_regress/tests.py
new file mode 100644
index 0000000..1d0d6ed
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_forms_regress/tests.py
@@ -0,0 +1,311 @@
+from datetime import date
+
+from django import db, forms
+from django.conf import settings
+from django.forms.models import modelform_factory, ModelChoiceField
+from django.test import TestCase
+
+from models import Person, RealPerson, Triple, FilePathModel, Article, \
+ Publication, CustomFF, Author, Author1, Homepage
+
+
+class ModelMultipleChoiceFieldTests(TestCase):
+
+ def setUp(self):
+ self.old_debug = settings.DEBUG
+ settings.DEBUG = True
+
+ def tearDown(self):
+ settings.DEBUG = self.old_debug
+
+ def test_model_multiple_choice_number_of_queries(self):
+ """
+ Test that ModelMultipleChoiceField does O(1) queries instead of
+ O(n) (#10156).
+ """
+ for i in range(30):
+ Person.objects.create(name="Person %s" % i)
+
+ db.reset_queries()
+ f = forms.ModelMultipleChoiceField(queryset=Person.objects.all())
+ selected = f.clean([1, 3, 5, 7, 9])
+ self.assertEquals(len(db.connection.queries), 1)
+
+ def test_model_multiple_choice_run_validators(self):
+ """
+ Test that ModelMultipleChoiceField run given validators (#14144).
+ """
+ for i in range(30):
+ Person.objects.create(name="Person %s" % i)
+
+ self._validator_run = False
+ def my_validator(value):
+ self._validator_run = True
+
+ f = forms.ModelMultipleChoiceField(queryset=Person.objects.all(),
+ validators=[my_validator])
+
+ f.clean([p.pk for p in Person.objects.all()[8:9]])
+ self.assertTrue(self._validator_run)
+
+class TripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+
+class UniqueTogetherTests(TestCase):
+ def test_multiple_field_unique_together(self):
+ """
+ When the same field is involved in multiple unique_together
+ constraints, we need to make sure we don't remove the data for it
+ before doing all the validation checking (not just failing after
+ the first one).
+ """
+ Triple.objects.create(left=1, middle=2, right=3)
+
+ form = TripleForm({'left': '1', 'middle': '2', 'right': '3'})
+ self.assertFalse(form.is_valid())
+
+ form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
+ self.assertTrue(form.is_valid())
+
+class TripleFormWithCleanOverride(forms.ModelForm):
+ class Meta:
+ model = Triple
+
+ def clean(self):
+ if not self.cleaned_data['left'] == self.cleaned_data['right']:
+ raise forms.ValidationError('Left and right should be equal')
+ return self.cleaned_data
+
+class OverrideCleanTests(TestCase):
+ def test_override_clean(self):
+ """
+ Regression for #12596: Calling super from ModelForm.clean() should be
+ optional.
+ """
+ form = TripleFormWithCleanOverride({'left': 1, 'middle': 2, 'right': 1})
+ self.assertTrue(form.is_valid())
+ # form.instance.left will be None if the instance was not constructed
+ # by form.full_clean().
+ self.assertEquals(form.instance.left, 1)
+
+# Regression test for #12960.
+# Make sure the cleaned_data returned from ModelForm.clean() is applied to the
+# model instance.
+
+class PublicationForm(forms.ModelForm):
+ def clean(self):
+ self.cleaned_data['title'] = self.cleaned_data['title'].upper()
+ return self.cleaned_data
+
+ class Meta:
+ model = Publication
+
+class ModelFormCleanTest(TestCase):
+ def test_model_form_clean_applies_to_model(self):
+ data = {'title': 'test', 'date_published': '2010-2-25'}
+ form = PublicationForm(data)
+ publication = form.save()
+ self.assertEqual(publication.title, 'TEST')
+
+class FPForm(forms.ModelForm):
+ class Meta:
+ model = FilePathModel
+
+class FilePathFieldTests(TestCase):
+ def test_file_path_field_blank(self):
+ """
+ Regression test for #8842: FilePathField(blank=True)
+ """
+ form = FPForm()
+ names = [p[1] for p in form['path'].field.choices]
+ names.sort()
+ self.assertEqual(names, ['---------', '__init__.py', 'models.py', 'tests.py'])
+
+class ManyToManyCallableInitialTests(TestCase):
+ def test_callable(self):
+ "Regression for #10349: A callable can be provided as the initial value for an m2m field"
+
+ # Set up a callable initial value
+ def formfield_for_dbfield(db_field, **kwargs):
+ if db_field.name == 'publications':
+ kwargs['initial'] = lambda: Publication.objects.all().order_by('date_published')[:2]
+ return db_field.formfield(**kwargs)
+
+ # Set up some Publications to use as data
+ Publication(title="First Book", date_published=date(2007,1,1)).save()
+ Publication(title="Second Book", date_published=date(2008,1,1)).save()
+ Publication(title="Third Book", date_published=date(2009,1,1)).save()
+
+ # Create a ModelForm, instantiate it, and check that the output is as expected
+ ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield)
+ form = ModelForm()
+ self.assertEquals(form.as_ul(), u"""<li><label for="id_headline">Headline:</label> <input id="id_headline" type="text" name="headline" maxlength="100" /></li>
+<li><label for="id_publications">Publications:</label> <select multiple="multiple" name="publications" id="id_publications">
+<option value="1" selected="selected">First Book</option>
+<option value="2" selected="selected">Second Book</option>
+<option value="3">Third Book</option>
+</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>""")
+
+class CFFForm(forms.ModelForm):
+ class Meta:
+ model = CustomFF
+
+class CustomFieldSaveTests(TestCase):
+ def test_save(self):
+ "Regression for #11149: save_form_data should be called only once"
+
+ # It's enough that the form saves without error -- the custom save routine will
+ # generate an AssertionError if it is called more than once during save.
+ form = CFFForm(data = {'f': None})
+ form.save()
+
+class ModelChoiceIteratorTests(TestCase):
+ def test_len(self):
+ class Form(forms.ModelForm):
+ class Meta:
+ model = Article
+ fields = ["publications"]
+
+ Publication.objects.create(title="Pravda",
+ date_published=date(1991, 8, 22))
+ f = Form()
+ self.assertEqual(len(f.fields["publications"].choices), 1)
+
+class RealPersonForm(forms.ModelForm):
+ class Meta:
+ model = RealPerson
+
+class CustomModelFormSaveMethod(TestCase):
+ def test_string_message(self):
+ data = {'name': 'anonymous'}
+ form = RealPersonForm(data)
+ self.assertEqual(form.is_valid(), False)
+ self.assertEqual(form.errors['__all__'], ['Please specify a real name.'])
+
+class ModelClassTests(TestCase):
+ def test_no_model_class(self):
+ class NoModelModelForm(forms.ModelForm):
+ pass
+ self.assertRaises(ValueError, NoModelModelForm)
+
+class OneToOneFieldTests(TestCase):
+ def test_assignment_of_none(self):
+ class AuthorForm(forms.ModelForm):
+ class Meta:
+ model = Author
+ fields = ['publication', 'full_name']
+
+ publication = Publication.objects.create(title="Pravda",
+ date_published=date(1991, 8, 22))
+ author = Author.objects.create(publication=publication, full_name='John Doe')
+ form = AuthorForm({'publication':u'', 'full_name':'John Doe'}, instance=author)
+ self.assert_(form.is_valid())
+ self.assertEqual(form.cleaned_data['publication'], None)
+ author = form.save()
+ # author object returned from form still retains original publication object
+ # that's why we need to retreive it from database again
+ new_author = Author.objects.get(pk=author.pk)
+ self.assertEqual(new_author.publication, None)
+
+ def test_assignment_of_none_null_false(self):
+ class AuthorForm(forms.ModelForm):
+ class Meta:
+ model = Author1
+ fields = ['publication', 'full_name']
+
+ publication = Publication.objects.create(title="Pravda",
+ date_published=date(1991, 8, 22))
+ author = Author1.objects.create(publication=publication, full_name='John Doe')
+ form = AuthorForm({'publication':u'', 'full_name':'John Doe'}, instance=author)
+ self.assert_(not form.is_valid())
+
+
+class ModelChoiceForm(forms.Form):
+ person = ModelChoiceField(Person.objects.all())
+
+
+class TestTicket11183(TestCase):
+ def test_11183(self):
+ form1 = ModelChoiceForm()
+ field1 = form1.fields['person']
+ # To allow the widget to change the queryset of field1.widget.choices correctly,
+ # without affecting other forms, the following must hold:
+ self.assert_(field1 is not ModelChoiceForm.base_fields['person'])
+ self.assert_(field1.widget.choices.field is field1)
+
+class HomepageForm(forms.ModelForm):
+ class Meta:
+ model = Homepage
+
+class URLFieldTests(TestCase):
+ def test_url_on_modelform(self):
+ "Check basic URL field validation on model forms"
+ self.assertFalse(HomepageForm({'url': 'foo'}).is_valid())
+ self.assertFalse(HomepageForm({'url': 'http://'}).is_valid())
+ self.assertFalse(HomepageForm({'url': 'http://example'}).is_valid())
+ self.assertFalse(HomepageForm({'url': 'http://example.'}).is_valid())
+ self.assertFalse(HomepageForm({'url': 'http://com.'}).is_valid())
+
+ self.assertTrue(HomepageForm({'url': 'http://localhost'}).is_valid())
+ self.assertTrue(HomepageForm({'url': 'http://example.com'}).is_valid())
+ self.assertTrue(HomepageForm({'url': 'http://www.example.com'}).is_valid())
+ self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000'}).is_valid())
+ self.assertTrue(HomepageForm({'url': 'http://www.example.com/test'}).is_valid())
+ self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000/test'}).is_valid())
+ self.assertTrue(HomepageForm({'url': 'http://example.com/foo/bar'}).is_valid())
+
+ def test_http_prefixing(self):
+ "If the http:// prefix is omitted on form input, the field adds it again. (Refs #13613)"
+ form = HomepageForm({'url': 'example.com'})
+ form.is_valid()
+ # self.assertTrue(form.is_valid())
+ # self.assertEquals(form.cleaned_data['url'], 'http://example.com/')
+
+ form = HomepageForm({'url': 'example.com/test'})
+ form.is_valid()
+ # self.assertTrue(form.is_valid())
+ # self.assertEquals(form.cleaned_data['url'], 'http://example.com/test')
+
+
+class FormFieldCallbackTests(TestCase):
+
+ def test_baseform_with_widgets_in_meta(self):
+ """Regression for #13095: Using base forms with widgets defined in Meta should not raise errors."""
+ widget = forms.Textarea()
+
+ class BaseForm(forms.ModelForm):
+ class Meta:
+ model = Person
+ widgets = {'name': widget}
+
+ Form = modelform_factory(Person, form=BaseForm)
+ self.assertTrue(Form.base_fields['name'].widget is widget)
+
+ def test_custom_callback(self):
+ """Test that a custom formfield_callback is used if provided"""
+
+ callback_args = []
+
+ def callback(db_field, **kwargs):
+ callback_args.append((db_field, kwargs))
+ return db_field.formfield(**kwargs)
+
+ widget = forms.Textarea()
+
+ class BaseForm(forms.ModelForm):
+ class Meta:
+ model = Person
+ widgets = {'name': widget}
+
+ _ = modelform_factory(Person, form=BaseForm,
+ formfield_callback=callback)
+ id_field, name_field = Person._meta.fields
+
+ self.assertEqual(callback_args,
+ [(id_field, {}), (name_field, {'widget': widget})])
+
+ def test_bad_callback(self):
+ # A bad callback provided by user still gives an error
+ self.assertRaises(TypeError, modelform_factory, Person,
+ formfield_callback='not a function or callable')
diff --git a/parts/django/tests/regressiontests/model_formsets_regress/__init__.py b/parts/django/tests/regressiontests/model_formsets_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_formsets_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/model_formsets_regress/models.py b/parts/django/tests/regressiontests/model_formsets_regress/models.py
new file mode 100644
index 0000000..bd12dd6
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_formsets_regress/models.py
@@ -0,0 +1,19 @@
+from django.db import models
+
+class User(models.Model):
+ username = models.CharField(max_length=12, unique=True)
+ serial = models.IntegerField()
+
+class UserSite(models.Model):
+ user = models.ForeignKey(User, to_field="username")
+ data = models.IntegerField()
+
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+
+class Restaurant(Place):
+ pass
+
+class Manager(models.Model):
+ retaurant = models.ForeignKey(Restaurant)
+ name = models.CharField(max_length=50)
diff --git a/parts/django/tests/regressiontests/model_formsets_regress/tests.py b/parts/django/tests/regressiontests/model_formsets_regress/tests.py
new file mode 100644
index 0000000..ee2a26f
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_formsets_regress/tests.py
@@ -0,0 +1,218 @@
+from django import forms
+from django.forms.models import modelform_factory, inlineformset_factory, modelformset_factory
+from django.test import TestCase
+
+from models import User, UserSite, Restaurant, Manager
+
+
+class InlineFormsetTests(TestCase):
+ def test_formset_over_to_field(self):
+ "A formset over a ForeignKey with a to_field can be saved. Regression for #10243"
+ Form = modelform_factory(User)
+ FormSet = inlineformset_factory(User, UserSite)
+
+ # Instantiate the Form and FormSet to prove
+ # you can create a form with no data
+ form = Form()
+ form_set = FormSet(instance=User())
+
+ # Now create a new User and UserSite instance
+ data = {
+ 'serial': u'1',
+ 'username': u'apollo13',
+ 'usersite_set-TOTAL_FORMS': u'1',
+ 'usersite_set-INITIAL_FORMS': u'0',
+ 'usersite_set-MAX_NUM_FORMS': u'0',
+ 'usersite_set-0-data': u'10',
+ 'usersite_set-0-user': u'apollo13'
+ }
+ user = User()
+ form = Form(data)
+ if form.is_valid():
+ user = form.save()
+ else:
+ self.fail('Errors found on form:%s' % form_set)
+
+ form_set = FormSet(data, instance=user)
+ if form_set.is_valid():
+ form_set.save()
+ usersite = UserSite.objects.all().values()
+ self.assertEqual(usersite[0]['data'], 10)
+ self.assertEqual(usersite[0]['user_id'], u'apollo13')
+ else:
+ self.fail('Errors found on formset:%s' % form_set.errors)
+
+ # Now update the UserSite instance
+ data = {
+ 'usersite_set-TOTAL_FORMS': u'1',
+ 'usersite_set-INITIAL_FORMS': u'1',
+ 'usersite_set-MAX_NUM_FORMS': u'0',
+ 'usersite_set-0-id': unicode(usersite[0]['id']),
+ 'usersite_set-0-data': u'11',
+ 'usersite_set-0-user': u'apollo13'
+ }
+ form_set = FormSet(data, instance=user)
+ if form_set.is_valid():
+ form_set.save()
+ usersite = UserSite.objects.all().values()
+ self.assertEqual(usersite[0]['data'], 11)
+ self.assertEqual(usersite[0]['user_id'], u'apollo13')
+ else:
+ self.fail('Errors found on formset:%s' % form_set.errors)
+
+ # Now add a new UserSite instance
+ data = {
+ 'usersite_set-TOTAL_FORMS': u'2',
+ 'usersite_set-INITIAL_FORMS': u'1',
+ 'usersite_set-MAX_NUM_FORMS': u'0',
+ 'usersite_set-0-id': unicode(usersite[0]['id']),
+ 'usersite_set-0-data': u'11',
+ 'usersite_set-0-user': u'apollo13',
+ 'usersite_set-1-data': u'42',
+ 'usersite_set-1-user': u'apollo13'
+ }
+ form_set = FormSet(data, instance=user)
+ if form_set.is_valid():
+ form_set.save()
+ usersite = UserSite.objects.all().values().order_by('data')
+ self.assertEqual(usersite[0]['data'], 11)
+ self.assertEqual(usersite[0]['user_id'], u'apollo13')
+ self.assertEqual(usersite[1]['data'], 42)
+ self.assertEqual(usersite[1]['user_id'], u'apollo13')
+ else:
+ self.fail('Errors found on formset:%s' % form_set.errors)
+
+ def test_formset_over_inherited_model(self):
+ "A formset over a ForeignKey with a to_field can be saved. Regression for #11120"
+ Form = modelform_factory(Restaurant)
+ FormSet = inlineformset_factory(Restaurant, Manager)
+
+ # Instantiate the Form and FormSet to prove
+ # you can create a form with no data
+ form = Form()
+ form_set = FormSet(instance=Restaurant())
+
+ # Now create a new Restaurant and Manager instance
+ data = {
+ 'name': u"Guido's House of Pasta",
+ 'manager_set-TOTAL_FORMS': u'1',
+ 'manager_set-INITIAL_FORMS': u'0',
+ 'manager_set-MAX_NUM_FORMS': u'0',
+ 'manager_set-0-name': u'Guido Van Rossum'
+ }
+ restaurant = User()
+ form = Form(data)
+ if form.is_valid():
+ restaurant = form.save()
+ else:
+ self.fail('Errors found on form:%s' % form_set)
+
+ form_set = FormSet(data, instance=restaurant)
+ if form_set.is_valid():
+ form_set.save()
+ manager = Manager.objects.all().values()
+ self.assertEqual(manager[0]['name'], 'Guido Van Rossum')
+ else:
+ self.fail('Errors found on formset:%s' % form_set.errors)
+
+ # Now update the Manager instance
+ data = {
+ 'manager_set-TOTAL_FORMS': u'1',
+ 'manager_set-INITIAL_FORMS': u'1',
+ 'manager_set-MAX_NUM_FORMS': u'0',
+ 'manager_set-0-id': unicode(manager[0]['id']),
+ 'manager_set-0-name': u'Terry Gilliam'
+ }
+ form_set = FormSet(data, instance=restaurant)
+ if form_set.is_valid():
+ form_set.save()
+ manager = Manager.objects.all().values()
+ self.assertEqual(manager[0]['name'], 'Terry Gilliam')
+ else:
+ self.fail('Errors found on formset:%s' % form_set.errors)
+
+ # Now add a new Manager instance
+ data = {
+ 'manager_set-TOTAL_FORMS': u'2',
+ 'manager_set-INITIAL_FORMS': u'1',
+ 'manager_set-MAX_NUM_FORMS': u'0',
+ 'manager_set-0-id': unicode(manager[0]['id']),
+ 'manager_set-0-name': u'Terry Gilliam',
+ 'manager_set-1-name': u'John Cleese'
+ }
+ form_set = FormSet(data, instance=restaurant)
+ if form_set.is_valid():
+ form_set.save()
+ manager = Manager.objects.all().values().order_by('name')
+ self.assertEqual(manager[0]['name'], 'John Cleese')
+ self.assertEqual(manager[1]['name'], 'Terry Gilliam')
+ else:
+ self.fail('Errors found on formset:%s' % form_set.errors)
+
+ def test_formset_with_none_instance(self):
+ "A formset with instance=None can be created. Regression for #11872"
+ Form = modelform_factory(User)
+ FormSet = inlineformset_factory(User, UserSite)
+
+ # Instantiate the Form and FormSet to prove
+ # you can create a formset with an instance of None
+ form = Form(instance=None)
+ formset = FormSet(instance=None)
+
+
+class CustomWidget(forms.CharField):
+ pass
+
+
+class UserSiteForm(forms.ModelForm):
+ class Meta:
+ model = UserSite
+ widgets = {'data': CustomWidget}
+
+
+class Callback(object):
+
+ def __init__(self):
+ self.log = []
+
+ def __call__(self, db_field, **kwargs):
+ self.log.append((db_field, kwargs))
+ return db_field.formfield(**kwargs)
+
+
+class FormfieldCallbackTests(TestCase):
+ """
+ Regression for #13095: Using base forms with widgets
+ defined in Meta should not raise errors.
+ """
+
+ def test_inlineformset_factory_default(self):
+ Formset = inlineformset_factory(User, UserSite, form=UserSiteForm)
+ form = Formset({}).forms[0]
+ self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+
+ def test_modelformset_factory_default(self):
+ Formset = modelformset_factory(UserSite, form=UserSiteForm)
+ form = Formset({}).forms[0]
+ self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+
+ def assertCallbackCalled(self, callback):
+ id_field, user_field, data_field = UserSite._meta.fields
+ expected_log = [
+ (id_field, {}),
+ (user_field, {}),
+ (data_field, {'widget': CustomWidget}),
+ ]
+ self.assertEqual(callback.log, expected_log)
+
+ def test_inlineformset_custom_callback(self):
+ callback = Callback()
+ inlineformset_factory(User, UserSite, form=UserSiteForm,
+ formfield_callback=callback)
+ self.assertCallbackCalled(callback)
+
+ def test_modelformset_custom_callback(self):
+ callback = Callback()
+ modelformset_factory(UserSite, form=UserSiteForm,
+ formfield_callback=callback)
+ self.assertCallbackCalled(callback)
diff --git a/parts/django/tests/regressiontests/model_inheritance_regress/__init__.py b/parts/django/tests/regressiontests/model_inheritance_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_inheritance_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/model_inheritance_regress/models.py b/parts/django/tests/regressiontests/model_inheritance_regress/models.py
new file mode 100644
index 0000000..787f163
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_inheritance_regress/models.py
@@ -0,0 +1,148 @@
+import datetime
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+ address = models.CharField(max_length=80)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return u"%s the place" % self.name
+
+class Restaurant(Place):
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the restaurant" % self.name
+
+class ItalianRestaurant(Restaurant):
+ serves_gnocchi = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the italian restaurant" % self.name
+
+class ParkingLot(Place):
+ # An explicit link to the parent (we can control the attribute name).
+ parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
+ capacity = models.IntegerField()
+
+ def __unicode__(self):
+ return u"%s the parking lot" % self.name
+
+class ParkingLot2(Place):
+ # In lieu of any other connector, an existing OneToOneField will be
+ # promoted to the primary key.
+ parent = models.OneToOneField(Place)
+
+class ParkingLot3(Place):
+ # The parent_link connector need not be the pk on the model.
+ primary_key = models.AutoField(primary_key=True)
+ parent = models.OneToOneField(Place, parent_link=True)
+
+class Supplier(models.Model):
+ restaurant = models.ForeignKey(Restaurant)
+
+class Wholesaler(Supplier):
+ retailer = models.ForeignKey(Supplier,related_name='wholesale_supplier')
+
+class Parent(models.Model):
+ created = models.DateTimeField(default=datetime.datetime.now)
+
+class Child(Parent):
+ name = models.CharField(max_length=10)
+
+class SelfRefParent(models.Model):
+ parent_data = models.IntegerField()
+ self_data = models.ForeignKey('self', null=True)
+
+class SelfRefChild(SelfRefParent):
+ child_data = models.IntegerField()
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100)
+ pub_date = models.DateTimeField()
+ class Meta:
+ ordering = ('-pub_date', 'headline')
+
+ def __unicode__(self):
+ return self.headline
+
+class ArticleWithAuthor(Article):
+ author = models.CharField(max_length=100)
+
+class M2MBase(models.Model):
+ articles = models.ManyToManyField(Article)
+
+class M2MChild(M2MBase):
+ name = models.CharField(max_length=50)
+
+class Evaluation(Article):
+ quality = models.IntegerField()
+
+ class Meta:
+ abstract = True
+
+class QualityControl(Evaluation):
+ assignee = models.CharField(max_length=50)
+
+class BaseM(models.Model):
+ base_name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.base_name
+
+class DerivedM(BaseM):
+ customPK = models.IntegerField(primary_key=True)
+ derived_name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return "PK = %d, base_name = %s, derived_name = %s" \
+ % (self.customPK, self.base_name, self.derived_name)
+
+class AuditBase(models.Model):
+ planned_date = models.DateField()
+
+ class Meta:
+ abstract = True
+ verbose_name_plural = u'Audits'
+
+class CertificationAudit(AuditBase):
+ class Meta(AuditBase.Meta):
+ abstract = True
+
+class InternalCertificationAudit(CertificationAudit):
+ auditing_dept = models.CharField(max_length=20)
+
+# Check that abstract classes don't get m2m tables autocreated.
+class Person(models.Model):
+ name = models.CharField(max_length=100)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+class AbstractEvent(models.Model):
+ name = models.CharField(max_length=100)
+ attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
+
+ class Meta:
+ abstract = True
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+class BirthdayParty(AbstractEvent):
+ pass
+
+class BachelorParty(AbstractEvent):
+ pass
+
+class MessyBachelorParty(BachelorParty):
+ pass
diff --git a/parts/django/tests/regressiontests/model_inheritance_regress/tests.py b/parts/django/tests/regressiontests/model_inheritance_regress/tests.py
new file mode 100644
index 0000000..dac2cb5
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_inheritance_regress/tests.py
@@ -0,0 +1,388 @@
+"""
+Regression tests for Model inheritance behaviour.
+"""
+
+import datetime
+from operator import attrgetter
+
+from django.test import TestCase
+
+from models import (Place, Restaurant, ItalianRestaurant, ParkingLot,
+ ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent,
+ SelfRefChild, ArticleWithAuthor, M2MChild, QualityControl, DerivedM,
+ Person, BirthdayParty, BachelorParty, MessyBachelorParty,
+ InternalCertificationAudit)
+
+
+class ModelInheritanceTest(TestCase):
+ def test_model_inheritance(self):
+ # Regression for #7350, #7202
+ # Check that when you create a Parent object with a specific reference
+ # to an existent child instance, saving the Parent doesn't duplicate
+ # the child. This behaviour is only activated during a raw save - it
+ # is mostly relevant to deserialization, but any sort of CORBA style
+ # 'narrow()' API would require a similar approach.
+
+ # Create a child-parent-grandparent chain
+ place1 = Place(
+ name="Guido's House of Pasta",
+ address='944 W. Fullerton')
+ place1.save_base(raw=True)
+ restaurant = Restaurant(
+ place_ptr=place1,
+ serves_hot_dogs=True,
+ serves_pizza=False)
+ restaurant.save_base(raw=True)
+ italian_restaurant = ItalianRestaurant(
+ restaurant_ptr=restaurant,
+ serves_gnocchi=True)
+ italian_restaurant.save_base(raw=True)
+
+ # Create a child-parent chain with an explicit parent link
+ place2 = Place(name='Main St', address='111 Main St')
+ place2.save_base(raw=True)
+ park = ParkingLot(parent=place2, capacity=100)
+ park.save_base(raw=True)
+
+ # Check that no extra parent objects have been created.
+ places = list(Place.objects.all())
+ self.assertEqual(places, [place1, place2])
+
+ dicts = list(Restaurant.objects.values('name','serves_hot_dogs'))
+ self.assertEqual(dicts, [{
+ 'name': u"Guido's House of Pasta",
+ 'serves_hot_dogs': True
+ }])
+
+ dicts = list(ItalianRestaurant.objects.values(
+ 'name','serves_hot_dogs','serves_gnocchi'))
+ self.assertEqual(dicts, [{
+ 'name': u"Guido's House of Pasta",
+ 'serves_gnocchi': True,
+ 'serves_hot_dogs': True,
+ }])
+
+ dicts = list(ParkingLot.objects.values('name','capacity'))
+ self.assertEqual(dicts, [{
+ 'capacity': 100,
+ 'name': u'Main St',
+ }])
+
+ # You can also update objects when using a raw save.
+ place1.name = "Guido's All New House of Pasta"
+ place1.save_base(raw=True)
+
+ restaurant.serves_hot_dogs = False
+ restaurant.save_base(raw=True)
+
+ italian_restaurant.serves_gnocchi = False
+ italian_restaurant.save_base(raw=True)
+
+ place2.name='Derelict lot'
+ place2.save_base(raw=True)
+
+ park.capacity = 50
+ park.save_base(raw=True)
+
+ # No extra parent objects after an update, either.
+ places = list(Place.objects.all())
+ self.assertEqual(places, [place2, place1])
+ self.assertEqual(places[0].name, 'Derelict lot')
+ self.assertEqual(places[1].name, "Guido's All New House of Pasta")
+
+ dicts = list(Restaurant.objects.values('name','serves_hot_dogs'))
+ self.assertEqual(dicts, [{
+ 'name': u"Guido's All New House of Pasta",
+ 'serves_hot_dogs': False,
+ }])
+
+ dicts = list(ItalianRestaurant.objects.values(
+ 'name', 'serves_hot_dogs', 'serves_gnocchi'))
+ self.assertEqual(dicts, [{
+ 'name': u"Guido's All New House of Pasta",
+ 'serves_gnocchi': False,
+ 'serves_hot_dogs': False,
+ }])
+
+ dicts = list(ParkingLot.objects.values('name','capacity'))
+ self.assertEqual(dicts, [{
+ 'capacity': 50,
+ 'name': u'Derelict lot',
+ }])
+
+ # If you try to raw_save a parent attribute onto a child object,
+ # the attribute will be ignored.
+
+ italian_restaurant.name = "Lorenzo's Pasta Hut"
+ italian_restaurant.save_base(raw=True)
+
+ # Note that the name has not changed
+ # - name is an attribute of Place, not ItalianRestaurant
+ dicts = list(ItalianRestaurant.objects.values(
+ 'name','serves_hot_dogs','serves_gnocchi'))
+ self.assertEqual(dicts, [{
+ 'name': u"Guido's All New House of Pasta",
+ 'serves_gnocchi': False,
+ 'serves_hot_dogs': False,
+ }])
+
+ def test_issue_7105(self):
+ # Regressions tests for #7105: dates() queries should be able to use
+ # fields from the parent model as easily as the child.
+ obj = Child.objects.create(
+ name='child',
+ created=datetime.datetime(2008, 6, 26, 17, 0, 0))
+ dates = list(Child.objects.dates('created', 'month'))
+ self.assertEqual(dates, [datetime.datetime(2008, 6, 1, 0, 0)])
+
+ def test_issue_7276(self):
+ # Regression test for #7276: calling delete() on a model with
+ # multi-table inheritance should delete the associated rows from any
+ # ancestor tables, as well as any descendent objects.
+ place1 = Place(
+ name="Guido's House of Pasta",
+ address='944 W. Fullerton')
+ place1.save_base(raw=True)
+ restaurant = Restaurant(
+ place_ptr=place1,
+ serves_hot_dogs=True,
+ serves_pizza=False)
+ restaurant.save_base(raw=True)
+ italian_restaurant = ItalianRestaurant(
+ restaurant_ptr=restaurant,
+ serves_gnocchi=True)
+ italian_restaurant.save_base(raw=True)
+
+ ident = ItalianRestaurant.objects.all()[0].id
+ self.assertEqual(Place.objects.get(pk=ident), place1)
+ xx = Restaurant.objects.create(
+ name='a',
+ address='xx',
+ serves_hot_dogs=True,
+ serves_pizza=False)
+
+ # This should delete both Restuarants, plus the related places, plus
+ # the ItalianRestaurant.
+ Restaurant.objects.all().delete()
+
+ self.assertRaises(
+ Place.DoesNotExist,
+ Place.objects.get,
+ pk=ident)
+ self.assertRaises(
+ ItalianRestaurant.DoesNotExist,
+ ItalianRestaurant.objects.get,
+ pk=ident)
+
+ def test_issue_6755(self):
+ """
+ Regression test for #6755
+ """
+ r = Restaurant(serves_pizza=False)
+ r.save()
+ self.assertEqual(r.id, r.place_ptr_id)
+ orig_id = r.id
+ r = Restaurant(place_ptr_id=orig_id, serves_pizza=True)
+ r.save()
+ self.assertEqual(r.id, orig_id)
+ self.assertEqual(r.id, r.place_ptr_id)
+
+ def test_issue_7488(self):
+ # Regression test for #7488. This looks a little crazy, but it's the
+ # equivalent of what the admin interface has to do for the edit-inline
+ # case.
+ suppliers = Supplier.objects.filter(
+ restaurant=Restaurant(name='xx', address='yy'))
+ suppliers = list(suppliers)
+ self.assertEqual(suppliers, [])
+
+ def test_issue_11764(self):
+ """
+ Regression test for #11764
+ """
+ wholesalers = list(Wholesaler.objects.all().select_related())
+ self.assertEqual(wholesalers, [])
+
+ def test_issue_7853(self):
+ """
+ Regression test for #7853
+ If the parent class has a self-referential link, make sure that any
+ updates to that link via the child update the right table.
+ """
+ obj = SelfRefChild.objects.create(child_data=37, parent_data=42)
+ obj.delete()
+
+ def test_get_next_previous_by_date(self):
+ """
+ Regression tests for #8076
+ get_(next/previous)_by_date should work
+ """
+ c1 = ArticleWithAuthor(
+ headline='ArticleWithAuthor 1',
+ author="Person 1",
+ pub_date=datetime.datetime(2005, 8, 1, 3, 0))
+ c1.save()
+ c2 = ArticleWithAuthor(
+ headline='ArticleWithAuthor 2',
+ author="Person 2",
+ pub_date=datetime.datetime(2005, 8, 1, 10, 0))
+ c2.save()
+ c3 = ArticleWithAuthor(
+ headline='ArticleWithAuthor 3',
+ author="Person 3",
+ pub_date=datetime.datetime(2005, 8, 2))
+ c3.save()
+
+ self.assertEqual(c1.get_next_by_pub_date(), c2)
+ self.assertEqual(c2.get_next_by_pub_date(), c3)
+ self.assertRaises(
+ ArticleWithAuthor.DoesNotExist,
+ c3.get_next_by_pub_date)
+ self.assertEqual(c3.get_previous_by_pub_date(), c2)
+ self.assertEqual(c2.get_previous_by_pub_date(), c1)
+ self.assertRaises(
+ ArticleWithAuthor.DoesNotExist,
+ c1.get_previous_by_pub_date)
+
+ def test_inherited_fields(self):
+ """
+ Regression test for #8825 and #9390
+ Make sure all inherited fields (esp. m2m fields, in this case) appear
+ on the child class.
+ """
+ m2mchildren = list(M2MChild.objects.filter(articles__isnull=False))
+ self.assertEqual(m2mchildren, [])
+
+ # Ordering should not include any database column more than once (this
+ # is most likely to ocurr naturally with model inheritance, so we
+ # check it here). Regression test for #9390. This necessarily pokes at
+ # the SQL string for the query, since the duplicate problems are only
+ # apparent at that late stage.
+ qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk')
+ sql = qs.query.get_compiler(qs.db).as_sql()[0]
+ fragment = sql[sql.find('ORDER BY'):]
+ pos = fragment.find('pub_date')
+ self.assertEqual(fragment.find('pub_date', pos + 1), -1)
+
+ def test_queryset_update_on_parent_model(self):
+ """
+ Regression test for #10362
+ It is possible to call update() and only change a field in
+ an ancestor model.
+ """
+ article = ArticleWithAuthor.objects.create(
+ author="fred",
+ headline="Hey there!",
+ pub_date=datetime.datetime(2009, 3, 1, 8, 0, 0))
+ update = ArticleWithAuthor.objects.filter(
+ author="fred").update(headline="Oh, no!")
+ self.assertEqual(update, 1)
+ update = ArticleWithAuthor.objects.filter(
+ pk=article.pk).update(headline="Oh, no!")
+ self.assertEqual(update, 1)
+
+ derivedm1 = DerivedM.objects.create(
+ customPK=44,
+ base_name="b1",
+ derived_name="d1")
+ self.assertEqual(derivedm1.customPK, 44)
+ self.assertEqual(derivedm1.base_name, 'b1')
+ self.assertEqual(derivedm1.derived_name, 'd1')
+ derivedms = list(DerivedM.objects.all())
+ self.assertEqual(derivedms, [derivedm1])
+
+ def test_use_explicit_o2o_to_parent_as_pk(self):
+ """
+ Regression tests for #10406
+ If there's a one-to-one link between a child model and the parent and
+ no explicit pk declared, we can use the one-to-one link as the pk on
+ the child.
+ """
+ self.assertEqual(ParkingLot2._meta.pk.name, "parent")
+
+ # However, the connector from child to parent need not be the pk on
+ # the child at all.
+ self.assertEqual(ParkingLot3._meta.pk.name, "primary_key")
+ # the child->parent link
+ self.assertEqual(
+ ParkingLot3._meta.get_ancestor_link(Place).name,
+ "parent")
+
+ def test_all_fields_from_abstract_base_class(self):
+ """
+ Regression tests for #7588
+ """
+ # All fields from an ABC, including those inherited non-abstractly
+ # should be available on child classes (#7588). Creating this instance
+ # should work without error.
+ QualityControl.objects.create(
+ headline="Problems in Django",
+ pub_date=datetime.datetime.now(),
+ quality=10,
+ assignee="adrian")
+
+ def test_abstract_base_class_m2m_relation_inheritance(self):
+ # Check that many-to-many relations defined on an abstract base class
+ # are correctly inherited (and created) on the child class.
+ p1 = Person.objects.create(name='Alice')
+ p2 = Person.objects.create(name='Bob')
+ p3 = Person.objects.create(name='Carol')
+ p4 = Person.objects.create(name='Dave')
+
+ birthday = BirthdayParty.objects.create(
+ name='Birthday party for Alice')
+ birthday.attendees = [p1, p3]
+
+ bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
+ bachelor.attendees = [p2, p4]
+
+ parties = list(p1.birthdayparty_set.all())
+ self.assertEqual(parties, [birthday])
+
+ parties = list(p1.bachelorparty_set.all())
+ self.assertEqual(parties, [])
+
+ parties = list(p2.bachelorparty_set.all())
+ self.assertEqual(parties, [bachelor])
+
+ # Check that a subclass of a subclass of an abstract model doesn't get
+ # it's own accessor.
+ self.assertFalse(hasattr(p2, 'messybachelorparty_set'))
+
+ # ... but it does inherit the m2m from it's parent
+ messy = MessyBachelorParty.objects.create(
+ name='Bachelor party for Dave')
+ messy.attendees = [p4]
+ messy_parent = messy.bachelorparty_ptr
+
+ parties = list(p4.bachelorparty_set.all())
+ self.assertEqual(parties, [bachelor, messy_parent])
+
+ def test_11369(self):
+ """
+ verbose_name_plural correctly inherited from ABC if inheritance chain
+ includes an abstract model.
+ """
+ # Regression test for #11369: verbose_name_plural should be inherited
+ # from an ABC even when there are one or more intermediate
+ # abstract models in the inheritance chain, for consistency with
+ # verbose_name.
+ self.assertEquals(
+ InternalCertificationAudit._meta.verbose_name_plural,
+ u'Audits'
+ )
+
+ def test_inherited_nullable_exclude(self):
+ obj = SelfRefChild.objects.create(child_data=37, parent_data=42)
+ self.assertQuerysetEqual(
+ SelfRefParent.objects.exclude(self_data=72), [
+ obj.pk
+ ],
+ attrgetter("pk")
+ )
+ self.assertQuerysetEqual(
+ SelfRefChild.objects.exclude(self_data=72), [
+ obj.pk
+ ],
+ attrgetter("pk")
+ )
diff --git a/parts/django/tests/regressiontests/model_inheritance_select_related/__init__.py b/parts/django/tests/regressiontests/model_inheritance_select_related/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_inheritance_select_related/__init__.py
diff --git a/parts/django/tests/regressiontests/model_inheritance_select_related/models.py b/parts/django/tests/regressiontests/model_inheritance_select_related/models.py
new file mode 100644
index 0000000..5851e46
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_inheritance_select_related/models.py
@@ -0,0 +1,29 @@
+"""
+Regression tests for the interaction between model inheritance and
+select_related().
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return u"%s the place" % self.name
+
+class Restaurant(Place):
+ serves_sushi = models.BooleanField()
+ serves_steak = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the restaurant" % self.name
+
+class Person(models.Model):
+ name = models.CharField(max_length=50)
+ favorite_restaurant = models.ForeignKey(Restaurant)
+
+ def __unicode__(self):
+ return self.name
diff --git a/parts/django/tests/regressiontests/model_inheritance_select_related/tests.py b/parts/django/tests/regressiontests/model_inheritance_select_related/tests.py
new file mode 100644
index 0000000..e1ed609
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_inheritance_select_related/tests.py
@@ -0,0 +1,29 @@
+from operator import attrgetter
+
+from django.test import TestCase
+
+from models import Restaurant, Person
+
+
+class ModelInheritanceSelectRelatedTests(TestCase):
+ def test_inherited_select_related(self):
+ # Regression test for #7246
+ r1 = Restaurant.objects.create(
+ name="Nobu", serves_sushi=True, serves_steak=False
+ )
+ r2 = Restaurant.objects.create(
+ name="Craft", serves_sushi=False, serves_steak=True
+ )
+ p1 = Person.objects.create(name="John", favorite_restaurant=r1)
+ p2 = Person.objects.create(name="Jane", favorite_restaurant=r2)
+
+ self.assertQuerysetEqual(
+ Person.objects.order_by("name").select_related(), [
+ "Jane",
+ "John",
+ ],
+ attrgetter("name")
+ )
+
+ jane = Person.objects.order_by("name").select_related("favorite_restaurant")[0]
+ self.assertEqual(jane.favorite_restaurant.name, "Craft")
diff --git a/parts/django/tests/regressiontests/model_regress/__init__.py b/parts/django/tests/regressiontests/model_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/model_regress/models.py b/parts/django/tests/regressiontests/model_regress/models.py
new file mode 100644
index 0000000..f30b3ee
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_regress/models.py
@@ -0,0 +1,59 @@
+# coding: utf-8
+from django.db import models
+
+
+CHOICES = (
+ (1, 'first'),
+ (2, 'second'),
+)
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100, default='Default headline')
+ pub_date = models.DateTimeField()
+ status = models.IntegerField(blank=True, null=True, choices=CHOICES)
+ misc_data = models.CharField(max_length=100, blank=True)
+ article_text = models.TextField()
+
+ class Meta:
+ ordering = ('pub_date','headline')
+ # A utf-8 verbose name (Ångström's Articles) to test they are valid.
+ verbose_name = "\xc3\x85ngstr\xc3\xb6m's Articles"
+
+ def __unicode__(self):
+ return self.headline
+
+class Movie(models.Model):
+ #5218: Test models with non-default primary keys / AutoFields
+ movie_id = models.AutoField(primary_key=True)
+ name = models.CharField(max_length=60)
+
+class Party(models.Model):
+ when = models.DateField(null=True)
+
+class Event(models.Model):
+ when = models.DateTimeField()
+
+class Department(models.Model):
+ id = models.PositiveIntegerField(primary_key=True)
+ name = models.CharField(max_length=200)
+
+ def __unicode__(self):
+ return self.name
+
+class Worker(models.Model):
+ department = models.ForeignKey(Department)
+ name = models.CharField(max_length=200)
+
+ def __unicode__(self):
+ return self.name
+
+class BrokenUnicodeMethod(models.Model):
+ name = models.CharField(max_length=7)
+
+ def __unicode__(self):
+ # Intentionally broken (trying to insert a unicode value into a str
+ # object).
+ return 'Názov: %s' % self.name
+
+class NonAutoPK(models.Model):
+ name = models.CharField(max_length=10, primary_key=True)
diff --git a/parts/django/tests/regressiontests/model_regress/tests.py b/parts/django/tests/regressiontests/model_regress/tests.py
new file mode 100644
index 0000000..8009839
--- /dev/null
+++ b/parts/django/tests/regressiontests/model_regress/tests.py
@@ -0,0 +1,175 @@
+import datetime
+from operator import attrgetter
+
+from django.conf import settings
+from django.core.exceptions import ValidationError
+from django.db import DEFAULT_DB_ALIAS
+from django.test import TestCase
+from django.utils import tzinfo
+
+from models import (Worker, Article, Party, Event, Department,
+ BrokenUnicodeMethod, NonAutoPK)
+
+
+
+class ModelTests(TestCase):
+ # The bug is that the following queries would raise:
+ # "TypeError: Related Field has invalid lookup: gte"
+ def test_related_gte_lookup(self):
+ """
+ Regression test for #10153: foreign key __gte lookups.
+ """
+ Worker.objects.filter(department__gte=0)
+
+ def test_related_lte_lookup(self):
+ """
+ Regression test for #10153: foreign key __lte lookups.
+ """
+ Worker.objects.filter(department__lte=0)
+
+ def test_empty_choice(self):
+ # NOTE: Part of the regression test here is merely parsing the model
+ # declaration. The verbose_name, in particular, did not always work.
+ a = Article.objects.create(
+ headline="Look at me!", pub_date=datetime.datetime.now()
+ )
+ # An empty choice field should return None for the display name.
+ self.assertEqual(a.get_status_display(), None)
+
+ # Empty strings should be returned as Unicode
+ a = Article.objects.get(pk=a.pk)
+ self.assertEqual(a.misc_data, u'')
+ self.assertEqual(type(a.misc_data), unicode)
+
+ def test_long_textfield(self):
+ # TextFields can hold more than 4000 characters (this was broken in
+ # Oracle).
+ a = Article.objects.create(
+ headline="Really, really big",
+ pub_date=datetime.datetime.now(),
+ article_text = "ABCDE" * 1000
+ )
+ a = Article.objects.get(pk=a.pk)
+ self.assertEqual
+ (len(a.article_text), 5000)
+
+ def test_date_lookup(self):
+ # Regression test for #659
+ Party.objects.create(when=datetime.datetime(1999, 12, 31))
+ Party.objects.create(when=datetime.datetime(1998, 12, 31))
+ Party.objects.create(when=datetime.datetime(1999, 1, 1))
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__month=2), []
+ )
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__month=1), [
+ datetime.date(1999, 1, 1)
+ ],
+ attrgetter("when")
+ )
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__month=12), [
+ datetime.date(1999, 12, 31),
+ datetime.date(1998, 12, 31),
+ ],
+ attrgetter("when")
+ )
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__year=1998), [
+ datetime.date(1998, 12, 31),
+ ],
+ attrgetter("when")
+ )
+ # Regression test for #8510
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__day="31"), [
+ datetime.date(1999, 12, 31),
+ datetime.date(1998, 12, 31),
+ ],
+ attrgetter("when")
+ )
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__month="12"), [
+ datetime.date(1999, 12, 31),
+ datetime.date(1998, 12, 31),
+ ],
+ attrgetter("when")
+ )
+ self.assertQuerysetEqual(
+ Party.objects.filter(when__year="1998"), [
+ datetime.date(1998, 12, 31),
+ ],
+ attrgetter("when")
+ )
+
+ def test_date_filter_null(self):
+ # Date filtering was failing with NULL date values in SQLite
+ # (regression test for #3501, amongst other things).
+ Party.objects.create(when=datetime.datetime(1999, 1, 1))
+ Party.objects.create()
+ p = Party.objects.filter(when__month=1)[0]
+ self.assertEqual(p.when, datetime.date(1999, 1, 1))
+ self.assertQuerysetEqual(
+ Party.objects.filter(pk=p.pk).dates("when", "month"), [
+ 1
+ ],
+ attrgetter("month")
+ )
+
+ def test_get_next_prev_by_field(self):
+ # Check that get_next_by_FIELD and get_previous_by_FIELD don't crash
+ # when we have usecs values stored on the database
+ #
+ # It crashed after the Field.get_db_prep_* refactor, because on most
+ # backends DateTimeFields supports usecs, but DateTimeField.to_python
+ # didn't recognize them. (Note that
+ # Model._get_next_or_previous_by_FIELD coerces values to strings)
+ Event.objects.create(when=datetime.datetime(2000, 1, 1, 16, 0, 0))
+ Event.objects.create(when=datetime.datetime(2000, 1, 1, 6, 1, 1))
+ Event.objects.create(when=datetime.datetime(2000, 1, 1, 13, 1, 1))
+ e = Event.objects.create(when=datetime.datetime(2000, 1, 1, 12, 0, 20, 24))
+
+ self.assertEqual(
+ e.get_next_by_when().when, datetime.datetime(2000, 1, 1, 13, 1, 1)
+ )
+ self.assertEqual(
+ e.get_previous_by_when().when, datetime.datetime(2000, 1, 1, 6, 1, 1)
+ )
+
+ def test_primary_key_foreign_key_types(self):
+ # Check Department and Worker (non-default PK type)
+ d = Department.objects.create(id=10, name="IT")
+ w = Worker.objects.create(department=d, name="Full-time")
+ self.assertEqual(unicode(w), "Full-time")
+
+ def test_broken_unicode(self):
+ # Models with broken unicode methods should still have a printable repr
+ b = BrokenUnicodeMethod.objects.create(name="Jerry")
+ self.assertEqual(repr(b), "<BrokenUnicodeMethod: [Bad Unicode data]>")
+
+ if settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"] not in [
+ "django.db.backends.mysql",
+ "django.db.backends.oracle"
+ ]:
+ def test_timezones(self):
+ # Saving an updating with timezone-aware datetime Python objects.
+ # Regression test for #10443.
+ # The idea is that all these creations and saving should work
+ # without crashing. It's not rocket science.
+ dt1 = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(600))
+ dt2 = datetime.datetime(2008, 8, 31, 17, 20, tzinfo=tzinfo.FixedOffset(600))
+ obj = Article.objects.create(
+ headline="A headline", pub_date=dt1, article_text="foo"
+ )
+ obj.pub_date = dt2
+ obj.save()
+ self.assertEqual(
+ Article.objects.filter(headline="A headline").update(pub_date=dt1),
+ 1
+ )
+
+class ModelValidationTest(TestCase):
+ def test_pk_validation(self):
+ one = NonAutoPK.objects.create(name="one")
+ again = NonAutoPK(name="one")
+ self.assertRaises(ValidationError, again.validate_unique)
diff --git a/parts/django/tests/regressiontests/modeladmin/__init__.py b/parts/django/tests/regressiontests/modeladmin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/modeladmin/__init__.py
diff --git a/parts/django/tests/regressiontests/modeladmin/models.py b/parts/django/tests/regressiontests/modeladmin/models.py
new file mode 100644
index 0000000..20dfe2c
--- /dev/null
+++ b/parts/django/tests/regressiontests/modeladmin/models.py
@@ -0,0 +1,36 @@
+# coding: utf-8
+from datetime import date
+
+from django.db import models
+from django.contrib.auth.models import User
+
+class Band(models.Model):
+ name = models.CharField(max_length=100)
+ bio = models.TextField()
+ sign_date = models.DateField()
+
+ def __unicode__(self):
+ return self.name
+
+class Concert(models.Model):
+ main_band = models.ForeignKey(Band, related_name='main_concerts')
+ opening_band = models.ForeignKey(Band, related_name='opening_concerts',
+ blank=True)
+ day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
+ transport = models.CharField(max_length=100, choices=(
+ (1, 'Plane'),
+ (2, 'Train'),
+ (3, 'Bus')
+ ), blank=True)
+
+class ValidationTestModel(models.Model):
+ name = models.CharField(max_length=100)
+ slug = models.SlugField()
+ users = models.ManyToManyField(User)
+ state = models.CharField(max_length=2, choices=(("CO", "Colorado"), ("WA", "Washington")))
+ is_active = models.BooleanField()
+ pub_date = models.DateTimeField()
+ band = models.ForeignKey(Band)
+
+class ValidationTestInlineModel(models.Model):
+ parent = models.ForeignKey(ValidationTestModel)
diff --git a/parts/django/tests/regressiontests/modeladmin/tests.py b/parts/django/tests/regressiontests/modeladmin/tests.py
new file mode 100644
index 0000000..b13a0ec
--- /dev/null
+++ b/parts/django/tests/regressiontests/modeladmin/tests.py
@@ -0,0 +1,1226 @@
+from datetime import date
+import unittest
+
+from django import forms
+from django.conf import settings
+from django.contrib.admin.options import ModelAdmin, TabularInline, \
+ HORIZONTAL, VERTICAL
+from django.contrib.admin.sites import AdminSite
+from django.contrib.admin.validation import validate
+from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
+from django.core.exceptions import ImproperlyConfigured
+from django.forms.models import BaseModelFormSet
+from django.forms.widgets import Select
+from django.test import TestCase
+
+from models import Band, Concert, ValidationTestModel, \
+ ValidationTestInlineModel
+
+
+# None of the following tests really depend on the content of the request,
+# so we'll just pass in None.
+request = None
+
+
+class ModelAdminTests(TestCase):
+
+ def setUp(self):
+ self.band = Band.objects.create(
+ name='The Doors',
+ bio='',
+ sign_date=date(1965, 1, 1),
+ )
+ self.site = AdminSite()
+
+ # form/fields/fieldsets interaction ##############################
+
+ def test_default_fields(self):
+ ma = ModelAdmin(Band, self.site)
+
+ self.assertEquals(ma.get_form(request).base_fields.keys(),
+ ['name', 'bio', 'sign_date'])
+
+ def test_default_fieldsets(self):
+ # fieldsets_add and fieldsets_change should return a special data structure that
+ # is used in the templates. They should generate the "right thing" whether we
+ # have specified a custom form, the fields argument, or nothing at all.
+ #
+ # Here's the default case. There are no custom form_add/form_change methods,
+ # no fields argument, and no fieldsets argument.
+ ma = ModelAdmin(Band, self.site)
+ self.assertEqual(ma.get_fieldsets(request),
+ [(None, {'fields': ['name', 'bio', 'sign_date']})])
+
+ self.assertEqual(ma.get_fieldsets(request, self.band),
+ [(None, {'fields': ['name', 'bio', 'sign_date']})])
+
+ def test_field_arguments(self):
+ # If we specify the fields argument, fieldsets_add and fielsets_change should
+ # just stick the fields into a formsets structure and return it.
+ class BandAdmin(ModelAdmin):
+ fields = ['name']
+
+ ma = BandAdmin(Band, self.site)
+
+ self.assertEqual( ma.get_fieldsets(request),
+ [(None, {'fields': ['name']})])
+
+ self.assertEqual(ma.get_fieldsets(request, self.band),
+ [(None, {'fields': ['name']})])
+
+ def test_field_arguments_restricted_on_form(self):
+ # If we specify fields or fieldsets, it should exclude fields on the Form class
+ # to the fields specified. This may cause errors to be raised in the db layer if
+ # required model fields arent in fields/fieldsets, but that's preferable to
+ # ghost errors where you have a field in your Form class that isn't being
+ # displayed because you forgot to add it to fields/fieldsets
+
+ # Using `fields`.
+ class BandAdmin(ModelAdmin):
+ fields = ['name']
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(), ['name'])
+ self.assertEqual(ma.get_form(request, self.band).base_fields.keys(),
+ ['name'])
+
+ # Using `fieldsets`.
+ class BandAdmin(ModelAdmin):
+ fieldsets = [(None, {'fields': ['name']})]
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(), ['name'])
+ self.assertEqual(ma.get_form(request, self.band).base_fields.keys(),
+ ['name'])
+
+ # Using `exclude`.
+ class BandAdmin(ModelAdmin):
+ exclude = ['bio']
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(),
+ ['name', 'sign_date'])
+
+ # You can also pass a tuple to `exclude`.
+ class BandAdmin(ModelAdmin):
+ exclude = ('bio',)
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(),
+ ['name', 'sign_date'])
+
+ # Using `fields` and `exclude`.
+ class BandAdmin(ModelAdmin):
+ fields = ['name', 'bio']
+ exclude = ['bio']
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(),
+ ['name'])
+
+ def test_custom_form_validation(self):
+ # If we specify a form, it should use it allowing custom validation to work
+ # properly. This won't, however, break any of the admin widgets or media.
+
+ class AdminBandForm(forms.ModelForm):
+ delete = forms.BooleanField()
+
+ class Meta:
+ model = Band
+
+ class BandAdmin(ModelAdmin):
+ form = AdminBandForm
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(),
+ ['name', 'bio', 'sign_date', 'delete'])
+
+ self.assertEqual(
+ type(ma.get_form(request).base_fields['sign_date'].widget),
+ AdminDateWidget)
+
+ def test_queryset_override(self):
+ # If we need to override the queryset of a ModelChoiceField in our custom form
+ # make sure that RelatedFieldWidgetWrapper doesn't mess that up.
+
+ band2 = Band(name='The Beatles', bio='', sign_date=date(1962, 1, 1))
+ band2.save()
+
+ class ConcertAdmin(ModelAdmin):
+ pass
+ ma = ConcertAdmin(Concert, self.site)
+ form = ma.get_form(request)()
+
+ self.assertEqual(str(form["main_band"]),
+ '<select name="main_band" id="id_main_band">\n'
+ '<option value="" selected="selected">---------</option>\n'
+ '<option value="%d">The Doors</option>\n'
+ '<option value="%d">The Beatles</option>\n'
+ '</select>' % (self.band.id, band2.id))
+
+ class AdminConcertForm(forms.ModelForm):
+ class Meta:
+ model = Concert
+
+ def __init__(self, *args, **kwargs):
+ super(AdminConcertForm, self).__init__(*args, **kwargs)
+ self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')
+
+ class ConcertAdmin(ModelAdmin):
+ form = AdminConcertForm
+
+ ma = ConcertAdmin(Concert, self.site)
+ form = ma.get_form(request)()
+
+ self.assertEqual(str(form["main_band"]),
+ '<select name="main_band" id="id_main_band">\n'
+ '<option value="" selected="selected">---------</option>\n'
+ '<option value="%d">The Doors</option>\n'
+ '</select>' % self.band.id)
+
+ # radio_fields behavior ###########################################
+
+ def test_default_foreign_key_widget(self):
+ # First, without any radio_fields specified, the widgets for ForeignKey
+ # and fields with choices specified ought to be a basic Select widget.
+ # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
+ # they need to be handled properly when type checking. For Select fields, all of
+ # the choices lists have a first entry of dashes.
+
+ cma = ModelAdmin(Concert, self.site)
+ cmafa = cma.get_form(request)
+
+ self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
+ Select)
+ self.assertEqual(
+ list(cmafa.base_fields['main_band'].widget.choices),
+ [(u'', u'---------'), (self.band.id, u'The Doors')])
+
+ self.assertEqual(
+ type(cmafa.base_fields['opening_band'].widget.widget), Select)
+ self.assertEqual(
+ list(cmafa.base_fields['opening_band'].widget.choices),
+ [(u'', u'---------'), (self.band.id, u'The Doors')])
+
+ self.assertEqual(type(cmafa.base_fields['day'].widget), Select)
+ self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
+ [('', '---------'), (1, 'Fri'), (2, 'Sat')])
+
+ self.assertEqual(type(cmafa.base_fields['transport'].widget),
+ Select)
+ self.assertEqual(
+ list(cmafa.base_fields['transport'].widget.choices),
+ [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])
+
+ def test_foreign_key_as_radio_field(self):
+ # Now specify all the fields as radio_fields. Widgets should now be
+ # RadioSelect, and the choices list should have a first entry of 'None' if
+ # blank=True for the model field. Finally, the widget should have the
+ # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
+
+ class ConcertAdmin(ModelAdmin):
+ radio_fields = {
+ 'main_band': HORIZONTAL,
+ 'opening_band': VERTICAL,
+ 'day': VERTICAL,
+ 'transport': HORIZONTAL,
+ }
+
+ cma = ConcertAdmin(Concert, self.site)
+ cmafa = cma.get_form(request)
+
+ self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
+ AdminRadioSelect)
+ self.assertEqual(cmafa.base_fields['main_band'].widget.attrs,
+ {'class': 'radiolist inline'})
+ self.assertEqual(list(cmafa.base_fields['main_band'].widget.choices),
+ [(self.band.id, u'The Doors')])
+
+ self.assertEqual(
+ type(cmafa.base_fields['opening_band'].widget.widget),
+ AdminRadioSelect)
+ self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs,
+ {'class': 'radiolist'})
+ self.assertEqual(
+ list(cmafa.base_fields['opening_band'].widget.choices),
+ [(u'', u'None'), (self.band.id, u'The Doors')])
+
+ self.assertEqual(type(cmafa.base_fields['day'].widget),
+ AdminRadioSelect)
+ self.assertEqual(cmafa.base_fields['day'].widget.attrs,
+ {'class': 'radiolist'})
+ self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
+ [(1, 'Fri'), (2, 'Sat')])
+
+ self.assertEqual(type(cmafa.base_fields['transport'].widget),
+ AdminRadioSelect)
+ self.assertEqual(cmafa.base_fields['transport'].widget.attrs,
+ {'class': 'radiolist inline'})
+ self.assertEqual(list(cmafa.base_fields['transport'].widget.choices),
+ [('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])
+
+ class AdminConcertForm(forms.ModelForm):
+ class Meta:
+ model = Concert
+ exclude = ('transport',)
+
+ class ConcertAdmin(ModelAdmin):
+ form = AdminConcertForm
+
+ ma = ConcertAdmin(Concert, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(),
+ ['main_band', 'opening_band', 'day'])
+
+ class AdminConcertForm(forms.ModelForm):
+ extra = forms.CharField()
+
+ class Meta:
+ model = Concert
+ fields = ['extra', 'transport']
+
+ class ConcertAdmin(ModelAdmin):
+ form = AdminConcertForm
+
+ ma = ConcertAdmin(Concert, self.site)
+ self.assertEqual(ma.get_form(request).base_fields.keys(),
+ ['extra', 'transport'])
+
+ class ConcertInline(TabularInline):
+ form = AdminConcertForm
+ model = Concert
+ fk_name = 'main_band'
+ can_delete = True
+
+ class BandAdmin(ModelAdmin):
+ inlines = [
+ ConcertInline
+ ]
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(
+ list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
+ ['extra', 'transport', 'id', 'DELETE', 'main_band'])
+
+
+class ValidationTests(unittest.TestCase):
+ def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ def test_validation_only_runs_in_debug(self):
+ # Ensure validation only runs when DEBUG = True
+ try:
+ settings.DEBUG = True
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ raw_id_fields = 10
+
+ site = AdminSite()
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
+ site.register,
+ ValidationTestModel,
+ ValidationTestModelAdmin,
+ )
+ finally:
+ settings.DEBUG = False
+
+ site = AdminSite()
+ site.register(ValidationTestModel, ValidationTestModelAdmin)
+
+ def test_raw_id_fields_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ raw_id_fields = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ raw_id_fields = ('non_existent_field',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ raw_id_fields = ('name',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.raw_id_fields[0]', 'name' must be either a ForeignKey or ManyToManyField.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ raw_id_fields = ('users',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_fieldsets_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.fieldsets' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = ({},)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.fieldsets[0]' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = ((),)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.fieldsets[0]' does not have exactly two elements.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = (("General", ()),)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.fieldsets[0][1]' must be a dictionary.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = (("General", {}),)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'fields' key is required in ValidationTestModelAdmin.fieldsets[0][1] field options dict.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = (("General", {"fields": ("non_existent_field",)}),)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = (("General", {"fields": ("name",)}),)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = (("General", {"fields": ("name",)}),)
+ fields = ["name",]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "Both fieldsets and fields are specified in ValidationTestModelAdmin.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fieldsets = [(None, {'fields': ['name', 'name']})]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "There are duplicate field(s) in ValidationTestModelAdmin.fieldsets",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ fields = ["name", "name"]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "There are duplicate field(s) in ValidationTestModelAdmin.fields",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ def test_form_validation(self):
+
+ class FakeForm(object):
+ pass
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ form = FakeForm
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "ValidationTestModelAdmin.form does not inherit from BaseModelForm.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ def test_fieldsets_with_custom_form_validation(self):
+
+ class BandAdmin(ModelAdmin):
+
+ fieldsets = (
+ ('Band', {
+ 'fields': ('non_existent_field',)
+ }),
+ )
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'BandAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form.",
+ validate,
+ BandAdmin,
+ Band,
+ )
+
+ class BandAdmin(ModelAdmin):
+ fieldsets = (
+ ('Band', {
+ 'fields': ('name',)
+ }),
+ )
+
+ validate(BandAdmin, Band)
+
+ class AdminBandForm(forms.ModelForm):
+ class Meta:
+ model = Band
+
+ class BandAdmin(ModelAdmin):
+ form = AdminBandForm
+
+ fieldsets = (
+ ('Band', {
+ 'fields': ('non_existent_field',)
+ }),
+ )
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'BandAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form.",
+ validate,
+ BandAdmin,
+ Band,
+ )
+
+ class AdminBandForm(forms.ModelForm):
+ delete = forms.BooleanField()
+
+ class Meta:
+ model = Band
+
+ class BandAdmin(ModelAdmin):
+ form = AdminBandForm
+
+ fieldsets = (
+ ('Band', {
+ 'fields': ('name', 'bio', 'sign_date', 'delete')
+ }),
+ )
+
+ validate(BandAdmin, Band)
+
+ def test_filter_vertical_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_vertical = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_vertical = ("non_existent_field",)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_vertical = ("name",)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.filter_vertical[0]' must be a ManyToManyField.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_vertical = ("users",)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_filter_horizontal_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_horizontal = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_horizontal = ("non_existent_field",)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_horizontal = ("name",)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.filter_horizontal[0]' must be a ManyToManyField.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ filter_horizontal = ("users",)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_radio_fields_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ radio_fields = ()
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.radio_fields' must be a dictionary.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ radio_fields = {"non_existent_field": None}
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ radio_fields = {"name": None}
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.radio_fields['name']' is neither an instance of ForeignKey nor does have choices set.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ radio_fields = {"state": None}
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.radio_fields['state']' is neither admin.HORIZONTAL nor admin.VERTICAL.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ radio_fields = {"state": VERTICAL}
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_prepopulated_fields_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ prepopulated_fields = ()
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ prepopulated_fields = {"non_existent_field": None}
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ prepopulated_fields = {"slug": ("non_existent_field",)}
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.prepopulated_fields['slug'][0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ prepopulated_fields = {"users": ("name",)}
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.prepopulated_fields['users']' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ prepopulated_fields = {"slug": ("name",)}
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_list_display_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_display' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display = ('non_existent_field',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "ValidationTestModelAdmin.list_display[0], 'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display = ('users',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_display[0]', 'users' is a ManyToManyField which is not supported.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display = ('name',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_list_display_links_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display_links = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_display_links' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display_links = ('non_existent_field',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_display_links[0]' refers to 'non_existent_field' that is neither a field, method or property of model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display_links = ('name',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_display_links[0]'refers to 'name' which is not defined in 'list_display'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display = ('name',)
+ list_display_links = ('name',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_list_filter_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_filter = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_filter' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_filter = ('non_existent_field',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_filter[0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_filter = ('is_active',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_list_per_page_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_per_page = 'hello'
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_per_page' should be a integer.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_per_page = 100
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_search_fields_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ search_fields = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.search_fields' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ def test_date_hierarchy_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ date_hierarchy = 'non_existent_field'
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ date_hierarchy = 'name'
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ date_hierarchy = 'pub_date'
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_ordering_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ ordering = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.ordering' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ ordering = ('non_existent_field',)
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.ordering[0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ ordering = ('?', 'name')
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.ordering' has the random ordering marker '?', but contains other fields as well. Please either remove '?' or the other fields.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ ordering = ('?',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ ordering = ('band__name',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ ordering = ('name',)
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_list_select_related_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_select_related = 1
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.list_select_related' should be a boolean.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_select_related = False
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_save_as_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ save_as = 1
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.save_as' should be a boolean.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ save_as = True
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_save_on_top_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ save_on_top = 1
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.save_on_top' should be a boolean.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ save_on_top = True
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_inlines_validation(self):
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = 10
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.inlines' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(object):
+ pass
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.inlines[0]' does not inherit from BaseModelAdmin.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(TabularInline):
+ pass
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'model' is a required attribute of 'ValidationTestModelAdmin.inlines[0]'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class SomethingBad(object):
+ pass
+
+ class ValidationTestInline(TabularInline):
+ model = SomethingBad
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestModelAdmin.inlines[0].model' does not inherit from models.Model.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_fields_validation(self):
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ fields = 10
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestInline.fields' must be a list or tuple.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ fields = ("non_existent_field",)
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestInline.fields' refers to field 'non_existent_field' that is missing from the form.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ def test_fk_name_validation(self):
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ fk_name = "non_existent_field"
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'ValidationTestInlineModel'.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ fk_name = "parent"
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_extra_validation(self):
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ extra = "hello"
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestInline.extra' should be a integer.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ extra = 2
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_max_num_validation(self):
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ max_num = "hello"
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestInline.max_num' should be an integer or None (default).",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ max_num = 2
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
+
+ def test_formset_validation(self):
+
+ class FakeFormSet(object):
+ pass
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ formset = FakeFormSet
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ self.assertRaisesErrorWithMessage(
+ ImproperlyConfigured,
+ "'ValidationTestInline.formset' does not inherit from BaseModelFormSet.",
+ validate,
+ ValidationTestModelAdmin,
+ ValidationTestModel,
+ )
+
+ class RealModelFormSet(BaseModelFormSet):
+ pass
+
+ class ValidationTestInline(TabularInline):
+ model = ValidationTestInlineModel
+ formset = RealModelFormSet
+
+ class ValidationTestModelAdmin(ModelAdmin):
+ inlines = [ValidationTestInline]
+
+ validate(ValidationTestModelAdmin, ValidationTestModel)
diff --git a/parts/django/tests/regressiontests/multiple_database/__init__.py b/parts/django/tests/regressiontests/multiple_database/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/__init__.py
diff --git a/parts/django/tests/regressiontests/multiple_database/fixtures/multidb-common.json b/parts/django/tests/regressiontests/multiple_database/fixtures/multidb-common.json
new file mode 100644
index 0000000..3313417
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/fixtures/multidb-common.json
@@ -0,0 +1,10 @@
+[
+ {
+ "pk": 1,
+ "model": "multiple_database.book",
+ "fields": {
+ "title": "The Definitive Guide to Django",
+ "published": "2009-7-8"
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/multiple_database/fixtures/multidb.default.json b/parts/django/tests/regressiontests/multiple_database/fixtures/multidb.default.json
new file mode 100644
index 0000000..379b18a
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/fixtures/multidb.default.json
@@ -0,0 +1,26 @@
+[
+ {
+ "pk": 1,
+ "model": "multiple_database.person",
+ "fields": {
+ "name": "Marty Alchin"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "multiple_database.person",
+ "fields": {
+ "name": "George Vilches"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "multiple_database.book",
+ "fields": {
+ "title": "Pro Django",
+ "published": "2008-12-16",
+ "authors": [["Marty Alchin"]],
+ "editor": ["George Vilches"]
+ }
+ }
+]
diff --git a/parts/django/tests/regressiontests/multiple_database/fixtures/multidb.other.json b/parts/django/tests/regressiontests/multiple_database/fixtures/multidb.other.json
new file mode 100644
index 0000000..c64f490
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/fixtures/multidb.other.json
@@ -0,0 +1,26 @@
+[
+ {
+ "pk": 1,
+ "model": "multiple_database.person",
+ "fields": {
+ "name": "Mark Pilgrim"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "multiple_database.person",
+ "fields": {
+ "name": "Chris Mills"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "multiple_database.book",
+ "fields": {
+ "title": "Dive into Python",
+ "published": "2009-5-4",
+ "authors": [["Mark Pilgrim"]],
+ "editor": ["Chris Mills"]
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/multiple_database/fixtures/pets.json b/parts/django/tests/regressiontests/multiple_database/fixtures/pets.json
new file mode 100644
index 0000000..89756a3
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/fixtures/pets.json
@@ -0,0 +1,18 @@
+[
+ {
+ "pk": 1,
+ "model": "multiple_database.pet",
+ "fields": {
+ "name": "Mr Bigglesworth",
+ "owner": 1
+ }
+ },
+ {
+ "pk": 2,
+ "model": "multiple_database.pet",
+ "fields": {
+ "name": "Spot",
+ "owner": 2
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/multiple_database/models.py b/parts/django/tests/regressiontests/multiple_database/models.py
new file mode 100644
index 0000000..ce71828
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/models.py
@@ -0,0 +1,76 @@
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+from django.db import models
+
+class Review(models.Model):
+ source = models.CharField(max_length=100)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey()
+
+ def __unicode__(self):
+ return self.source
+
+ class Meta:
+ ordering = ('source',)
+
+class PersonManager(models.Manager):
+ def get_by_natural_key(self, name):
+ return self.get(name=name)
+
+class Person(models.Model):
+ objects = PersonManager()
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ ordering = ('name',)
+
+# This book manager doesn't do anything interesting; it just
+# exists to strip out the 'extra_arg' argument to certain
+# calls. This argument is used to establish that the BookManager
+# is actually getting used when it should be.
+class BookManager(models.Manager):
+ def create(self, *args, **kwargs):
+ kwargs.pop('extra_arg', None)
+ return super(BookManager, self).create(*args, **kwargs)
+
+ def get_or_create(self, *args, **kwargs):
+ kwargs.pop('extra_arg', None)
+ return super(BookManager, self).get_or_create(*args, **kwargs)
+
+class Book(models.Model):
+ objects = BookManager()
+ title = models.CharField(max_length=100)
+ published = models.DateField()
+ authors = models.ManyToManyField(Person)
+ editor = models.ForeignKey(Person, null=True, related_name='edited')
+ reviews = generic.GenericRelation(Review)
+ pages = models.IntegerField(default=100)
+
+ def __unicode__(self):
+ return self.title
+
+ class Meta:
+ ordering = ('title',)
+
+class Pet(models.Model):
+ name = models.CharField(max_length=100)
+ owner = models.ForeignKey(Person)
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ ordering = ('name',)
+
+class UserProfile(models.Model):
+ user = models.OneToOneField(User, null=True)
+ flavor = models.CharField(max_length=100)
+
+ class Meta:
+ ordering = ('flavor',)
diff --git a/parts/django/tests/regressiontests/multiple_database/tests.py b/parts/django/tests/regressiontests/multiple_database/tests.py
new file mode 100644
index 0000000..05dca26
--- /dev/null
+++ b/parts/django/tests/regressiontests/multiple_database/tests.py
@@ -0,0 +1,1681 @@
+import datetime
+import pickle
+import sys
+from StringIO import StringIO
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.core import management
+from django.db import connections, router, DEFAULT_DB_ALIAS
+from django.db.utils import ConnectionRouter
+from django.test import TestCase
+
+from models import Book, Person, Pet, Review, UserProfile
+
+try:
+ # we only have these models if the user is using multi-db, it's safe the
+ # run the tests without them though.
+ from models import Article, article_using
+except ImportError:
+ pass
+
+class QueryTestCase(TestCase):
+ multi_db = True
+
+ def test_db_selection(self):
+ "Check that querysets will use the default databse by default"
+ self.assertEquals(Book.objects.db, DEFAULT_DB_ALIAS)
+ self.assertEquals(Book.objects.all().db, DEFAULT_DB_ALIAS)
+
+ self.assertEquals(Book.objects.using('other').db, 'other')
+
+ self.assertEquals(Book.objects.db_manager('other').db, 'other')
+ self.assertEquals(Book.objects.db_manager('other').all().db, 'other')
+
+ def test_default_creation(self):
+ "Objects created on the default database don't leak onto other databases"
+ # Create a book on the default database using create()
+ Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ # Create a book on the default database using a save
+ dive = Book()
+ dive.title="Dive into Python"
+ dive.published = datetime.date(2009, 5, 4)
+ dive.save()
+
+ # Check that book exists on the default database, but not on other database
+ try:
+ Book.objects.get(title="Pro Django")
+ Book.objects.using('default').get(title="Pro Django")
+ except Book.DoesNotExist:
+ self.fail('"Dive Into Python" should exist on default database')
+
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.using('other').get,
+ title="Pro Django"
+ )
+
+ try:
+ Book.objects.get(title="Dive into Python")
+ Book.objects.using('default').get(title="Dive into Python")
+ except Book.DoesNotExist:
+ self.fail('"Dive into Python" should exist on default database')
+
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.using('other').get,
+ title="Dive into Python"
+ )
+
+
+ def test_other_creation(self):
+ "Objects created on another database don't leak onto the default database"
+ # Create a book on the second database
+ Book.objects.using('other').create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ # Create a book on the default database using a save
+ dive = Book()
+ dive.title="Dive into Python"
+ dive.published = datetime.date(2009, 5, 4)
+ dive.save(using='other')
+
+ # Check that book exists on the default database, but not on other database
+ try:
+ Book.objects.using('other').get(title="Pro Django")
+ except Book.DoesNotExist:
+ self.fail('"Dive Into Python" should exist on other database')
+
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.get,
+ title="Pro Django"
+ )
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.using('default').get,
+ title="Pro Django"
+ )
+
+ try:
+ Book.objects.using('other').get(title="Dive into Python")
+ except Book.DoesNotExist:
+ self.fail('"Dive into Python" should exist on other database')
+
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.get,
+ title="Dive into Python"
+ )
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.using('default').get,
+ title="Dive into Python"
+ )
+
+ def test_basic_queries(self):
+ "Queries are constrained to a single database"
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ dive = Book.objects.using('other').get(published=datetime.date(2009, 5, 4))
+ self.assertEqual(dive.title, "Dive into Python")
+ self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published=datetime.date(2009, 5, 4))
+
+ dive = Book.objects.using('other').get(title__icontains="dive")
+ self.assertEqual(dive.title, "Dive into Python")
+ self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__icontains="dive")
+
+ dive = Book.objects.using('other').get(title__iexact="dive INTO python")
+ self.assertEqual(dive.title, "Dive into Python")
+ self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__iexact="dive INTO python")
+
+ dive = Book.objects.using('other').get(published__year=2009)
+ self.assertEqual(dive.title, "Dive into Python")
+ self.assertEqual(dive.published, datetime.date(2009, 5, 4))
+ self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published__year=2009)
+
+ years = Book.objects.using('other').dates('published', 'year')
+ self.assertEqual([o.year for o in years], [2009])
+ years = Book.objects.using('default').dates('published', 'year')
+ self.assertEqual([o.year for o in years], [])
+
+ months = Book.objects.using('other').dates('published', 'month')
+ self.assertEqual([o.month for o in months], [5])
+ months = Book.objects.using('default').dates('published', 'month')
+ self.assertEqual([o.month for o in months], [])
+
+ def test_m2m_separation(self):
+ "M2M fields are constrained to a single database"
+ # Create a book and author on the default database
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.create(name="Marty Alchin")
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+
+ # Save the author relations
+ pro.authors = [marty]
+ dive.authors = [mark]
+
+ # Inspect the m2m tables directly.
+ # There should be 1 entry in each database
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 1)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 1)
+
+ # Check that queries work across m2m joins
+ self.assertEquals(list(Book.objects.using('default').filter(authors__name='Marty Alchin').values_list('title', flat=True)),
+ [u'Pro Django'])
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Marty Alchin').values_list('title', flat=True)),
+ [])
+
+ self.assertEquals(list(Book.objects.using('default').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
+ [])
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ # Reget the objects to clear caches
+ dive = Book.objects.using('other').get(title="Dive into Python")
+ mark = Person.objects.using('other').get(name="Mark Pilgrim")
+
+ # Retrive related object by descriptor. Related objects should be database-baound
+ self.assertEquals(list(dive.authors.all().values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
+ self.assertEquals(list(mark.book_set.all().values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ def test_m2m_forward_operations(self):
+ "M2M forward manipulations are all constrained to a single DB"
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+
+ # Save the author relations
+ dive.authors = [mark]
+
+ # Add a second author
+ john = Person.objects.using('other').create(name="John Smith")
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
+ [])
+
+
+ dive.authors.add(john)
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ # Remove the second author
+ dive.authors.remove(john)
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
+ [])
+
+ # Clear all authors
+ dive.authors.clear()
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
+ [])
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)),
+ [])
+
+ # Create an author through the m2m interface
+ dive.authors.create(name='Jane Brown')
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)),
+ [])
+ self.assertEquals(list(Book.objects.using('other').filter(authors__name='Jane Brown').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ def test_m2m_reverse_operations(self):
+ "M2M reverse manipulations are all constrained to a single DB"
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+
+ # Save the author relations
+ dive.authors = [mark]
+
+ # Create a second book on the other database
+ grease = Book.objects.using('other').create(title="Greasemonkey Hacks",
+ published=datetime.date(2005, 11, 1))
+
+ # Add a books to the m2m
+ mark.book_set.add(grease)
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
+ # Remove a book from the m2m
+ mark.book_set.remove(grease)
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)),
+ [])
+
+ # Clear the books associated with mark
+ mark.book_set.clear()
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
+ [])
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)),
+ [])
+
+ # Create a book through the m2m interface
+ mark.book_set.create(title="Dive into HTML5", published=datetime.date(2020, 1, 1))
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)),
+ [])
+ self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into HTML5').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
+ def test_m2m_cross_database_protection(self):
+ "Operations that involve sharing M2M objects across databases raise an error"
+ # Create a book and author on the default database
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.create(name="Marty Alchin")
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ # Set a foreign key set with an object from a different database
+ try:
+ marty.book_set = [pro, dive]
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Add to an m2m with an object from a different database
+ try:
+ marty.book_set.add(dive)
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Set a m2m with an object from a different database
+ try:
+ marty.book_set = [pro, dive]
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Add to a reverse m2m with an object from a different database
+ try:
+ dive.authors.add(marty)
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Set a reverse m2m with an object from a different database
+ try:
+ dive.authors = [mark, marty]
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ def test_m2m_deletion(self):
+ "Cascaded deletions of m2m relations issue queries on the right database"
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ dive.authors = [mark]
+
+ # Check the initial state
+ self.assertEquals(Person.objects.using('default').count(), 0)
+ self.assertEquals(Book.objects.using('default').count(), 0)
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+ self.assertEquals(Person.objects.using('other').count(), 1)
+ self.assertEquals(Book.objects.using('other').count(), 1)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 1)
+
+ # Delete the object on the other database
+ dive.delete(using='other')
+
+ self.assertEquals(Person.objects.using('default').count(), 0)
+ self.assertEquals(Book.objects.using('default').count(), 0)
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+ # The person still exists ...
+ self.assertEquals(Person.objects.using('other').count(), 1)
+ # ... but the book has been deleted
+ self.assertEquals(Book.objects.using('other').count(), 0)
+ # ... and the relationship object has also been deleted.
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ # Now try deletion in the reverse direction. Set up the relation again
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+ dive.authors = [mark]
+
+ # Check the initial state
+ self.assertEquals(Person.objects.using('default').count(), 0)
+ self.assertEquals(Book.objects.using('default').count(), 0)
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+ self.assertEquals(Person.objects.using('other').count(), 1)
+ self.assertEquals(Book.objects.using('other').count(), 1)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 1)
+
+ # Delete the object on the other database
+ mark.delete(using='other')
+
+ self.assertEquals(Person.objects.using('default').count(), 0)
+ self.assertEquals(Book.objects.using('default').count(), 0)
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+
+ # The person has been deleted ...
+ self.assertEquals(Person.objects.using('other').count(), 0)
+ # ... but the book still exists
+ self.assertEquals(Book.objects.using('other').count(), 1)
+ # ... and the relationship object has been deleted.
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ def test_foreign_key_separation(self):
+ "FK fields are constrained to a single database"
+ # Create a book and author on the default database
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.create(name="Marty Alchin")
+ george = Person.objects.create(name="George Vilches")
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ chris = Person.objects.using('other').create(name="Chris Mills")
+
+ # Save the author's favourite books
+ pro.editor = george
+ pro.save()
+
+ dive.editor = chris
+ dive.save()
+
+ pro = Book.objects.using('default').get(title="Pro Django")
+ self.assertEquals(pro.editor.name, "George Vilches")
+
+ dive = Book.objects.using('other').get(title="Dive into Python")
+ self.assertEquals(dive.editor.name, "Chris Mills")
+
+ # Check that queries work across foreign key joins
+ self.assertEquals(list(Person.objects.using('default').filter(edited__title='Pro Django').values_list('name', flat=True)),
+ [u'George Vilches'])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Pro Django').values_list('name', flat=True)),
+ [])
+
+ self.assertEquals(list(Person.objects.using('default').filter(edited__title='Dive into Python').values_list('name', flat=True)),
+ [])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
+ [u'Chris Mills'])
+
+ # Reget the objects to clear caches
+ chris = Person.objects.using('other').get(name="Chris Mills")
+ dive = Book.objects.using('other').get(title="Dive into Python")
+
+ # Retrive related object by descriptor. Related objects should be database-baound
+ self.assertEquals(list(chris.edited.values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ def test_foreign_key_reverse_operations(self):
+ "FK reverse manipulations are all constrained to a single DB"
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ chris = Person.objects.using('other').create(name="Chris Mills")
+
+ # Save the author relations
+ dive.editor = chris
+ dive.save()
+
+ # Add a second book edited by chris
+ html5 = Book.objects.using('other').create(title="Dive into HTML5", published=datetime.date(2010, 3, 15))
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
+ [])
+
+ chris.edited.add(html5)
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
+ [u'Chris Mills'])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
+ [u'Chris Mills'])
+
+ # Remove the second editor
+ chris.edited.remove(html5)
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
+ [])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
+ [u'Chris Mills'])
+
+ # Clear all edited books
+ chris.edited.clear()
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
+ [])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
+ [])
+
+ # Create an author through the m2m interface
+ chris.edited.create(title='Dive into Water', published=datetime.date(2010, 3, 15))
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)),
+ [])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Water').values_list('name', flat=True)),
+ [u'Chris Mills'])
+ self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)),
+ [])
+
+ def test_foreign_key_cross_database_protection(self):
+ "Operations that involve sharing FK objects across databases raise an error"
+ # Create a book and author on the default database
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.create(name="Marty Alchin")
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+
+ # Set a foreign key with an object from a different database
+ try:
+ dive.editor = marty
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Set a foreign key set with an object from a different database
+ try:
+ marty.edited = [pro, dive]
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Add to a foreign key set with an object from a different database
+ try:
+ marty.edited.add(dive)
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # BUT! if you assign a FK object when the base object hasn't
+ # been saved yet, you implicitly assign the database for the
+ # base object.
+ chris = Person(name="Chris Mills")
+ html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15))
+ # initially, no db assigned
+ self.assertEquals(chris._state.db, None)
+ self.assertEquals(html5._state.db, None)
+
+ # old object comes from 'other', so the new object is set to use 'other'...
+ dive.editor = chris
+ html5.editor = mark
+ self.assertEquals(chris._state.db, 'other')
+ self.assertEquals(html5._state.db, 'other')
+ # ... but it isn't saved yet
+ self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)),
+ [u'Mark Pilgrim'])
+ self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),
+ [u'Dive into Python'])
+
+ # When saved (no using required), new objects goes to 'other'
+ chris.save()
+ html5.save()
+ self.assertEquals(list(Person.objects.using('default').values_list('name',flat=True)),
+ [u'Marty Alchin'])
+ self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)),
+ [u'Chris Mills', u'Mark Pilgrim'])
+ self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)),
+ [u'Pro Django'])
+ self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),
+ [u'Dive into HTML5', u'Dive into Python'])
+
+ # This also works if you assign the FK in the constructor
+ water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark)
+ self.assertEquals(water._state.db, 'other')
+ # ... but it isn't saved yet
+ self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)),
+ [u'Pro Django'])
+ self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),
+ [u'Dive into HTML5', u'Dive into Python'])
+
+ # When saved, the new book goes to 'other'
+ water.save()
+ self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)),
+ [u'Pro Django'])
+ self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),
+ [u'Dive into HTML5', u'Dive into Python', u'Dive into Water'])
+
+ def test_foreign_key_deletion(self):
+ "Cascaded deletions of Foreign Key relations issue queries on the right database"
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ fido = Pet.objects.using('other').create(name="Fido", owner=mark)
+
+ # Check the initial state
+ self.assertEquals(Person.objects.using('default').count(), 0)
+ self.assertEquals(Pet.objects.using('default').count(), 0)
+
+ self.assertEquals(Person.objects.using('other').count(), 1)
+ self.assertEquals(Pet.objects.using('other').count(), 1)
+
+ # Delete the person object, which will cascade onto the pet
+ mark.delete(using='other')
+
+ self.assertEquals(Person.objects.using('default').count(), 0)
+ self.assertEquals(Pet.objects.using('default').count(), 0)
+
+ # Both the pet and the person have been deleted from the right database
+ self.assertEquals(Person.objects.using('other').count(), 0)
+ self.assertEquals(Pet.objects.using('other').count(), 0)
+
+ def test_foreign_key_validation(self):
+ "ForeignKey.validate() uses the correct database"
+ mickey = Person.objects.using('other').create(name="Mickey")
+ pluto = Pet.objects.using('other').create(name="Pluto", owner=mickey)
+ self.assertEquals(None, pluto.full_clean())
+
+ def test_o2o_separation(self):
+ "OneToOne fields are constrained to a single database"
+ # Create a user and profile on the default database
+ alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
+ alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
+
+ # Create a user and profile on the other database
+ bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
+ bob_profile = UserProfile.objects.using('other').create(user=bob, flavor='crunchy frog')
+
+ # Retrieve related objects; queries should be database constrained
+ alice = User.objects.using('default').get(username="alice")
+ self.assertEquals(alice.userprofile.flavor, "chocolate")
+
+ bob = User.objects.using('other').get(username="bob")
+ self.assertEquals(bob.userprofile.flavor, "crunchy frog")
+
+ # Check that queries work across joins
+ self.assertEquals(list(User.objects.using('default').filter(userprofile__flavor='chocolate').values_list('username', flat=True)),
+ [u'alice'])
+ self.assertEquals(list(User.objects.using('other').filter(userprofile__flavor='chocolate').values_list('username', flat=True)),
+ [])
+
+ self.assertEquals(list(User.objects.using('default').filter(userprofile__flavor='crunchy frog').values_list('username', flat=True)),
+ [])
+ self.assertEquals(list(User.objects.using('other').filter(userprofile__flavor='crunchy frog').values_list('username', flat=True)),
+ [u'bob'])
+
+ # Reget the objects to clear caches
+ alice_profile = UserProfile.objects.using('default').get(flavor='chocolate')
+ bob_profile = UserProfile.objects.using('other').get(flavor='crunchy frog')
+
+ # Retrive related object by descriptor. Related objects should be database-baound
+ self.assertEquals(alice_profile.user.username, 'alice')
+ self.assertEquals(bob_profile.user.username, 'bob')
+
+ def test_o2o_cross_database_protection(self):
+ "Operations that involve sharing FK objects across databases raise an error"
+ # Create a user and profile on the default database
+ alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
+
+ # Create a user and profile on the other database
+ bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
+
+ # Set a one-to-one relation with an object from a different database
+ alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
+ try:
+ bob.userprofile = alice_profile
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # BUT! if you assign a FK object when the base object hasn't
+ # been saved yet, you implicitly assign the database for the
+ # base object.
+ bob_profile = UserProfile.objects.using('other').create(user=bob, flavor='crunchy frog')
+
+ new_bob_profile = UserProfile(flavor="spring surprise")
+
+ charlie = User(username='charlie',email='charlie@example.com')
+ charlie.set_unusable_password()
+
+ # initially, no db assigned
+ self.assertEquals(new_bob_profile._state.db, None)
+ self.assertEquals(charlie._state.db, None)
+
+ # old object comes from 'other', so the new object is set to use 'other'...
+ new_bob_profile.user = bob
+ charlie.userprofile = bob_profile
+ self.assertEquals(new_bob_profile._state.db, 'other')
+ self.assertEquals(charlie._state.db, 'other')
+
+ # ... but it isn't saved yet
+ self.assertEquals(list(User.objects.using('other').values_list('username',flat=True)),
+ [u'bob'])
+ self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
+ [u'crunchy frog'])
+
+ # When saved (no using required), new objects goes to 'other'
+ charlie.save()
+ bob_profile.save()
+ new_bob_profile.save()
+ self.assertEquals(list(User.objects.using('default').values_list('username',flat=True)),
+ [u'alice'])
+ self.assertEquals(list(User.objects.using('other').values_list('username',flat=True)),
+ [u'bob', u'charlie'])
+ self.assertEquals(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
+ [u'chocolate'])
+ self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
+ [u'crunchy frog', u'spring surprise'])
+
+ # This also works if you assign the O2O relation in the constructor
+ denise = User.objects.db_manager('other').create_user('denise','denise@example.com')
+ denise_profile = UserProfile(flavor="tofu", user=denise)
+
+ self.assertEquals(denise_profile._state.db, 'other')
+ # ... but it isn't saved yet
+ self.assertEquals(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
+ [u'chocolate'])
+ self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
+ [u'crunchy frog', u'spring surprise'])
+
+ # When saved, the new profile goes to 'other'
+ denise_profile.save()
+ self.assertEquals(list(UserProfile.objects.using('default').values_list('flavor',flat=True)),
+ [u'chocolate'])
+ self.assertEquals(list(UserProfile.objects.using('other').values_list('flavor',flat=True)),
+ [u'crunchy frog', u'spring surprise', u'tofu'])
+
+ def test_generic_key_separation(self):
+ "Generic fields are constrained to a single database"
+ # Create a book and author on the default database
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ review1 = Review.objects.create(source="Python Monthly", content_object=pro)
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
+
+ review1 = Review.objects.using('default').get(source="Python Monthly")
+ self.assertEquals(review1.content_object.title, "Pro Django")
+
+ review2 = Review.objects.using('other').get(source="Python Weekly")
+ self.assertEquals(review2.content_object.title, "Dive into Python")
+
+ # Reget the objects to clear caches
+ dive = Book.objects.using('other').get(title="Dive into Python")
+
+ # Retrive related object by descriptor. Related objects should be database-bound
+ self.assertEquals(list(dive.reviews.all().values_list('source', flat=True)),
+ [u'Python Weekly'])
+
+ def test_generic_key_reverse_operations(self):
+ "Generic reverse manipulations are all constrained to a single DB"
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ temp = Book.objects.using('other').create(title="Temp",
+ published=datetime.date(2009, 5, 4))
+
+ review1 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
+ review2 = Review.objects.using('other').create(source="Python Monthly", content_object=temp)
+
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [u'Python Weekly'])
+
+ # Add a second review
+ dive.reviews.add(review2)
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [u'Python Monthly', u'Python Weekly'])
+
+ # Remove the second author
+ dive.reviews.remove(review1)
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [u'Python Monthly'])
+
+ # Clear all reviews
+ dive.reviews.clear()
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [])
+
+ # Create an author through the generic interface
+ dive.reviews.create(source='Python Daily')
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)),
+ [u'Python Daily'])
+
+ def test_generic_key_cross_database_protection(self):
+ "Operations that involve sharing generic key objects across databases raise an error"
+ # Create a book and author on the default database
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ review1 = Review.objects.create(source="Python Monthly", content_object=pro)
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
+
+ # Set a foreign key with an object from a different database
+ try:
+ review1.content_object = dive
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # Add to a foreign key set with an object from a different database
+ try:
+ dive.reviews.add(review1)
+ self.fail("Shouldn't be able to assign across databases")
+ except ValueError:
+ pass
+
+ # BUT! if you assign a FK object when the base object hasn't
+ # been saved yet, you implicitly assign the database for the
+ # base object.
+ review3 = Review(source="Python Daily")
+ # initially, no db assigned
+ self.assertEquals(review3._state.db, None)
+
+ # Dive comes from 'other', so review3 is set to use 'other'...
+ review3.content_object = dive
+ self.assertEquals(review3._state.db, 'other')
+ # ... but it isn't saved yet
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)),
+ [u'Python Monthly'])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
+ [u'Python Weekly'])
+
+ # When saved, John goes to 'other'
+ review3.save()
+ self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)),
+ [u'Python Monthly'])
+ self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
+ [u'Python Daily', u'Python Weekly'])
+
+ def test_generic_key_deletion(self):
+ "Cascaded deletions of Generic Key relations issue queries on the right database"
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+ review = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
+
+ # Check the initial state
+ self.assertEquals(Book.objects.using('default').count(), 0)
+ self.assertEquals(Review.objects.using('default').count(), 0)
+
+ self.assertEquals(Book.objects.using('other').count(), 1)
+ self.assertEquals(Review.objects.using('other').count(), 1)
+
+ # Delete the Book object, which will cascade onto the pet
+ dive.delete(using='other')
+
+ self.assertEquals(Book.objects.using('default').count(), 0)
+ self.assertEquals(Review.objects.using('default').count(), 0)
+
+ # Both the pet and the person have been deleted from the right database
+ self.assertEquals(Book.objects.using('other').count(), 0)
+ self.assertEquals(Review.objects.using('other').count(), 0)
+
+ def test_ordering(self):
+ "get_next_by_XXX commands stick to a single database"
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ learn = Book.objects.using('other').create(title="Learning Python",
+ published=datetime.date(2008, 7, 16))
+
+ self.assertEquals(learn.get_next_by_published().title, "Dive into Python")
+ self.assertEquals(dive.get_previous_by_published().title, "Learning Python")
+
+ def test_raw(self):
+ "test the raw() method across databases"
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+ val = Book.objects.db_manager("other").raw('SELECT id FROM multiple_database_book')
+ self.assertEqual(map(lambda o: o.pk, val), [dive.pk])
+
+ val = Book.objects.raw('SELECT id FROM multiple_database_book').using('other')
+ self.assertEqual(map(lambda o: o.pk, val), [dive.pk])
+
+ def test_select_related(self):
+ "Database assignment is retained if an object is retrieved with select_related()"
+ # Create a book and author on the other database
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4),
+ editor=mark)
+
+ # Retrieve the Person using select_related()
+ book = Book.objects.using('other').select_related('editor').get(title="Dive into Python")
+
+ # The editor instance should have a db state
+ self.assertEqual(book.editor._state.db, 'other')
+
+ def test_subquery(self):
+ """Make sure as_sql works with subqueries and master/slave."""
+ sub = Person.objects.using('other').filter(name='fff')
+ qs = Book.objects.filter(editor__in=sub)
+
+ # When you call __str__ on the query object, it doesn't know about using
+ # so it falls back to the default. If the subquery explicitly uses a
+ # different database, an error should be raised.
+ self.assertRaises(ValueError, str, qs.query)
+
+ # Evaluating the query shouldn't work, either
+ try:
+ for obj in qs:
+ pass
+ self.fail('Iterating over query should raise ValueError')
+ except ValueError:
+ pass
+
+ def test_related_manager(self):
+ "Related managers return managers, not querysets"
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+
+ # extra_arg is removed by the BookManager's implementation of
+ # create(); but the BookManager's implementation won't get called
+ # unless edited returns a Manager, not a queryset
+ mark.book_set.create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4),
+ extra_arg=True)
+
+ mark.book_set.get_or_create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4),
+ extra_arg=True)
+
+ mark.edited.create(title="Dive into Water",
+ published=datetime.date(2009, 5, 4),
+ extra_arg=True)
+
+ mark.edited.get_or_create(title="Dive into Water",
+ published=datetime.date(2009, 5, 4),
+ extra_arg=True)
+
+class TestRouter(object):
+ # A test router. The behaviour is vaguely master/slave, but the
+ # databases aren't assumed to propagate changes.
+ def db_for_read(self, model, instance=None, **hints):
+ if instance:
+ return instance._state.db or 'other'
+ return 'other'
+
+ def db_for_write(self, model, **hints):
+ return DEFAULT_DB_ALIAS
+
+ def allow_relation(self, obj1, obj2, **hints):
+ return obj1._state.db in ('default', 'other') and obj2._state.db in ('default', 'other')
+
+ def allow_syncdb(self, db, model):
+ return True
+
+class AuthRouter(object):
+ """A router to control all database operations on models in
+ the contrib.auth application"""
+
+ def db_for_read(self, model, **hints):
+ "Point all read operations on auth models to 'default'"
+ if model._meta.app_label == 'auth':
+ # We use default here to ensure we can tell the difference
+ # between a read request and a write request for Auth objects
+ return 'default'
+ return None
+
+ def db_for_write(self, model, **hints):
+ "Point all operations on auth models to 'other'"
+ if model._meta.app_label == 'auth':
+ return 'other'
+ return None
+
+ def allow_relation(self, obj1, obj2, **hints):
+ "Allow any relation if a model in Auth is involved"
+ if obj1._meta.app_label == 'auth' or obj2._meta.app_label == 'auth':
+ return True
+ return None
+
+ def allow_syncdb(self, db, model):
+ "Make sure the auth app only appears on the 'other' db"
+ if db == 'other':
+ return model._meta.app_label == 'auth'
+ elif model._meta.app_label == 'auth':
+ return False
+ return None
+
+class WriteRouter(object):
+ # A router that only expresses an opinion on writes
+ def db_for_write(self, model, **hints):
+ return 'writer'
+
+class RouterTestCase(TestCase):
+ multi_db = True
+
+ def setUp(self):
+ # Make the 'other' database appear to be a slave of the 'default'
+ self.old_routers = router.routers
+ router.routers = [TestRouter()]
+
+ def tearDown(self):
+ # Restore the 'other' database as an independent database
+ router.routers = self.old_routers
+
+ def test_db_selection(self):
+ "Check that querysets obey the router for db suggestions"
+ self.assertEquals(Book.objects.db, 'other')
+ self.assertEquals(Book.objects.all().db, 'other')
+
+ self.assertEquals(Book.objects.using('default').db, 'default')
+
+ self.assertEquals(Book.objects.db_manager('default').db, 'default')
+ self.assertEquals(Book.objects.db_manager('default').all().db, 'default')
+
+ def test_syncdb_selection(self):
+ "Synchronization behaviour is predicatable"
+
+ self.assertTrue(router.allow_syncdb('default', User))
+ self.assertTrue(router.allow_syncdb('default', Book))
+
+ self.assertTrue(router.allow_syncdb('other', User))
+ self.assertTrue(router.allow_syncdb('other', Book))
+
+ # Add the auth router to the chain.
+ # TestRouter is a universal synchronizer, so it should have no effect.
+ router.routers = [TestRouter(), AuthRouter()]
+
+ self.assertTrue(router.allow_syncdb('default', User))
+ self.assertTrue(router.allow_syncdb('default', Book))
+
+ self.assertTrue(router.allow_syncdb('other', User))
+ self.assertTrue(router.allow_syncdb('other', Book))
+
+ # Now check what happens if the router order is the other way around
+ router.routers = [AuthRouter(), TestRouter()]
+
+ self.assertFalse(router.allow_syncdb('default', User))
+ self.assertTrue(router.allow_syncdb('default', Book))
+
+ self.assertTrue(router.allow_syncdb('other', User))
+ self.assertFalse(router.allow_syncdb('other', Book))
+
+ def test_partial_router(self):
+ "A router can choose to implement a subset of methods"
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ # First check the baseline behaviour
+
+ self.assertEquals(router.db_for_read(User), 'other')
+ self.assertEquals(router.db_for_read(Book), 'other')
+
+ self.assertEquals(router.db_for_write(User), 'default')
+ self.assertEquals(router.db_for_write(Book), 'default')
+
+ self.assertTrue(router.allow_relation(dive, dive))
+
+ self.assertTrue(router.allow_syncdb('default', User))
+ self.assertTrue(router.allow_syncdb('default', Book))
+
+ router.routers = [WriteRouter(), AuthRouter(), TestRouter()]
+
+ self.assertEquals(router.db_for_read(User), 'default')
+ self.assertEquals(router.db_for_read(Book), 'other')
+
+ self.assertEquals(router.db_for_write(User), 'writer')
+ self.assertEquals(router.db_for_write(Book), 'writer')
+
+ self.assertTrue(router.allow_relation(dive, dive))
+
+ self.assertFalse(router.allow_syncdb('default', User))
+ self.assertTrue(router.allow_syncdb('default', Book))
+
+
+ def test_database_routing(self):
+ marty = Person.objects.using('default').create(name="Marty Alchin")
+ pro = Book.objects.using('default').create(title="Pro Django",
+ published=datetime.date(2008, 12, 16),
+ editor=marty)
+ pro.authors = [marty]
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ # An update query will be routed to the default database
+ Book.objects.filter(title='Pro Django').update(pages=200)
+
+ try:
+ # By default, the get query will be directed to 'other'
+ Book.objects.get(title='Pro Django')
+ self.fail("Shouldn't be able to find the book")
+ except Book.DoesNotExist:
+ pass
+
+ # But the same query issued explicitly at a database will work.
+ pro = Book.objects.using('default').get(title='Pro Django')
+
+ # Check that the update worked.
+ self.assertEquals(pro.pages, 200)
+
+ # An update query with an explicit using clause will be routed
+ # to the requested database.
+ Book.objects.using('other').filter(title='Dive into Python').update(pages=300)
+ self.assertEquals(Book.objects.get(title='Dive into Python').pages, 300)
+
+ # Related object queries stick to the same database
+ # as the original object, regardless of the router
+ self.assertEquals(list(pro.authors.values_list('name', flat=True)), [u'Marty Alchin'])
+ self.assertEquals(pro.editor.name, u'Marty Alchin')
+
+ # get_or_create is a special case. The get needs to be targetted at
+ # the write database in order to avoid potential transaction
+ # consistency problems
+ book, created = Book.objects.get_or_create(title="Pro Django")
+ self.assertFalse(created)
+
+ book, created = Book.objects.get_or_create(title="Dive Into Python",
+ defaults={'published':datetime.date(2009, 5, 4)})
+ self.assertTrue(created)
+
+ # Check the head count of objects
+ self.assertEquals(Book.objects.using('default').count(), 2)
+ self.assertEquals(Book.objects.using('other').count(), 1)
+ # If a database isn't specified, the read database is used
+ self.assertEquals(Book.objects.count(), 1)
+
+ # A delete query will also be routed to the default database
+ Book.objects.filter(pages__gt=150).delete()
+
+ # The default database has lost the book.
+ self.assertEquals(Book.objects.using('default').count(), 1)
+ self.assertEquals(Book.objects.using('other').count(), 1)
+
+ def test_foreign_key_cross_database_protection(self):
+ "Foreign keys can cross databases if they two databases have a common source"
+ # Create a book and author on the default database
+ pro = Book.objects.using('default').create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.using('default').create(name="Marty Alchin")
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+
+ # Set a foreign key with an object from a different database
+ try:
+ dive.editor = marty
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments of original objects haven't changed...
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(mark._state.db, 'other')
+
+ # ... but they will when the affected object is saved.
+ dive.save()
+ self.assertEquals(dive._state.db, 'default')
+
+ # ...and the source database now has a copy of any object saved
+ try:
+ Book.objects.using('default').get(title='Dive into Python').delete()
+ except Book.DoesNotExist:
+ self.fail('Source database should have a copy of saved object')
+
+ # This isn't a real master-slave database, so restore the original from other
+ dive = Book.objects.using('other').get(title='Dive into Python')
+ self.assertEquals(dive._state.db, 'other')
+
+ # Set a foreign key set with an object from a different database
+ try:
+ marty.edited = [pro, dive]
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Assignment implies a save, so database assignments of original objects have changed...
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'default')
+ self.assertEquals(mark._state.db, 'other')
+
+ # ...and the source database now has a copy of any object saved
+ try:
+ Book.objects.using('default').get(title='Dive into Python').delete()
+ except Book.DoesNotExist:
+ self.fail('Source database should have a copy of saved object')
+
+ # This isn't a real master-slave database, so restore the original from other
+ dive = Book.objects.using('other').get(title='Dive into Python')
+ self.assertEquals(dive._state.db, 'other')
+
+ # Add to a foreign key set with an object from a different database
+ try:
+ marty.edited.add(dive)
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Add implies a save, so database assignments of original objects have changed...
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'default')
+ self.assertEquals(mark._state.db, 'other')
+
+ # ...and the source database now has a copy of any object saved
+ try:
+ Book.objects.using('default').get(title='Dive into Python').delete()
+ except Book.DoesNotExist:
+ self.fail('Source database should have a copy of saved object')
+
+ # This isn't a real master-slave database, so restore the original from other
+ dive = Book.objects.using('other').get(title='Dive into Python')
+
+ # If you assign a FK object when the base object hasn't
+ # been saved yet, you implicitly assign the database for the
+ # base object.
+ chris = Person(name="Chris Mills")
+ html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15))
+ # initially, no db assigned
+ self.assertEquals(chris._state.db, None)
+ self.assertEquals(html5._state.db, None)
+
+ # old object comes from 'other', so the new object is set to use the
+ # source of 'other'...
+ self.assertEquals(dive._state.db, 'other')
+ dive.editor = chris
+ html5.editor = mark
+
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(mark._state.db, 'other')
+ self.assertEquals(chris._state.db, 'default')
+ self.assertEquals(html5._state.db, 'default')
+
+ # This also works if you assign the FK in the constructor
+ water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark)
+ self.assertEquals(water._state.db, 'default')
+
+ # If you create an object through a FK relation, it will be
+ # written to the write database, even if the original object
+ # was on the read database
+ cheesecake = mark.edited.create(title='Dive into Cheesecake', published=datetime.date(2010, 3, 15))
+ self.assertEquals(cheesecake._state.db, 'default')
+
+ # Same goes for get_or_create, regardless of whether getting or creating
+ cheesecake, created = mark.edited.get_or_create(title='Dive into Cheesecake', published=datetime.date(2010, 3, 15))
+ self.assertEquals(cheesecake._state.db, 'default')
+
+ puddles, created = mark.edited.get_or_create(title='Dive into Puddles', published=datetime.date(2010, 3, 15))
+ self.assertEquals(puddles._state.db, 'default')
+
+ def test_m2m_cross_database_protection(self):
+ "M2M relations can cross databases if the database share a source"
+ # Create books and authors on the inverse to the usual database
+ pro = Book.objects.using('other').create(pk=1, title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.using('other').create(pk=1, name="Marty Alchin")
+
+ dive = Book.objects.using('default').create(pk=2, title="Dive into Python",
+ published=datetime.date(2009, 5, 4))
+
+ mark = Person.objects.using('default').create(pk=2, name="Mark Pilgrim")
+
+ # Now save back onto the usual databse.
+ # This simulates master/slave - the objects exist on both database,
+ # but the _state.db is as it is for all other tests.
+ pro.save(using='default')
+ marty.save(using='default')
+ dive.save(using='other')
+ mark.save(using='other')
+
+ # Check that we have 2 of both types of object on both databases
+ self.assertEquals(Book.objects.using('default').count(), 2)
+ self.assertEquals(Book.objects.using('other').count(), 2)
+ self.assertEquals(Person.objects.using('default').count(), 2)
+ self.assertEquals(Person.objects.using('other').count(), 2)
+
+ # Set a m2m set with an object from a different database
+ try:
+ marty.book_set = [pro, dive]
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments don't change
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(mark._state.db, 'other')
+
+ # All m2m relations should be saved on the default database
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 2)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ # Reset relations
+ Book.authors.through.objects.using('default').delete()
+
+ # Add to an m2m with an object from a different database
+ try:
+ marty.book_set.add(dive)
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments don't change
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(mark._state.db, 'other')
+
+ # All m2m relations should be saved on the default database
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 1)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ # Reset relations
+ Book.authors.through.objects.using('default').delete()
+
+ # Set a reverse m2m with an object from a different database
+ try:
+ dive.authors = [mark, marty]
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments don't change
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(mark._state.db, 'other')
+
+ # All m2m relations should be saved on the default database
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 2)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ # Reset relations
+ Book.authors.through.objects.using('default').delete()
+
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 0)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ # Add to a reverse m2m with an object from a different database
+ try:
+ dive.authors.add(marty)
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments don't change
+ self.assertEquals(marty._state.db, 'default')
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(mark._state.db, 'other')
+
+ # All m2m relations should be saved on the default database
+ self.assertEquals(Book.authors.through.objects.using('default').count(), 1)
+ self.assertEquals(Book.authors.through.objects.using('other').count(), 0)
+
+ # If you create an object through a M2M relation, it will be
+ # written to the write database, even if the original object
+ # was on the read database
+ alice = dive.authors.create(name='Alice')
+ self.assertEquals(alice._state.db, 'default')
+
+ # Same goes for get_or_create, regardless of whether getting or creating
+ alice, created = dive.authors.get_or_create(name='Alice')
+ self.assertEquals(alice._state.db, 'default')
+
+ bob, created = dive.authors.get_or_create(name='Bob')
+ self.assertEquals(bob._state.db, 'default')
+
+ def test_o2o_cross_database_protection(self):
+ "Operations that involve sharing FK objects across databases raise an error"
+ # Create a user and profile on the default database
+ alice = User.objects.db_manager('default').create_user('alice', 'alice@example.com')
+
+ # Create a user and profile on the other database
+ bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
+
+ # Set a one-to-one relation with an object from a different database
+ alice_profile = UserProfile.objects.create(user=alice, flavor='chocolate')
+ try:
+ bob.userprofile = alice_profile
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments of original objects haven't changed...
+ self.assertEquals(alice._state.db, 'default')
+ self.assertEquals(alice_profile._state.db, 'default')
+ self.assertEquals(bob._state.db, 'other')
+
+ # ... but they will when the affected object is saved.
+ bob.save()
+ self.assertEquals(bob._state.db, 'default')
+
+ def test_generic_key_cross_database_protection(self):
+ "Generic Key operations can span databases if they share a source"
+ # Create a book and author on the default database
+ pro = Book.objects.using('default'
+ ).create(title="Pro Django", published=datetime.date(2008, 12, 16))
+
+ review1 = Review.objects.using('default'
+ ).create(source="Python Monthly", content_object=pro)
+
+ # Create a book and author on the other database
+ dive = Book.objects.using('other'
+ ).create(title="Dive into Python", published=datetime.date(2009, 5, 4))
+
+ review2 = Review.objects.using('other'
+ ).create(source="Python Weekly", content_object=dive)
+
+ # Set a generic foreign key with an object from a different database
+ try:
+ review1.content_object = dive
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments of original objects haven't changed...
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(review1._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(review2._state.db, 'other')
+
+ # ... but they will when the affected object is saved.
+ dive.save()
+ self.assertEquals(review1._state.db, 'default')
+ self.assertEquals(dive._state.db, 'default')
+
+ # ...and the source database now has a copy of any object saved
+ try:
+ Book.objects.using('default').get(title='Dive into Python').delete()
+ except Book.DoesNotExist:
+ self.fail('Source database should have a copy of saved object')
+
+ # This isn't a real master-slave database, so restore the original from other
+ dive = Book.objects.using('other').get(title='Dive into Python')
+ self.assertEquals(dive._state.db, 'other')
+
+ # Add to a generic foreign key set with an object from a different database
+ try:
+ dive.reviews.add(review1)
+ except ValueError:
+ self.fail("Assignment across master/slave databases with a common source should be ok")
+
+ # Database assignments of original objects haven't changed...
+ self.assertEquals(pro._state.db, 'default')
+ self.assertEquals(review1._state.db, 'default')
+ self.assertEquals(dive._state.db, 'other')
+ self.assertEquals(review2._state.db, 'other')
+
+ # ... but they will when the affected object is saved.
+ dive.save()
+ self.assertEquals(dive._state.db, 'default')
+
+ # ...and the source database now has a copy of any object saved
+ try:
+ Book.objects.using('default').get(title='Dive into Python').delete()
+ except Book.DoesNotExist:
+ self.fail('Source database should have a copy of saved object')
+
+ # BUT! if you assign a FK object when the base object hasn't
+ # been saved yet, you implicitly assign the database for the
+ # base object.
+ review3 = Review(source="Python Daily")
+ # initially, no db assigned
+ self.assertEquals(review3._state.db, None)
+
+ # Dive comes from 'other', so review3 is set to use the source of 'other'...
+ review3.content_object = dive
+ self.assertEquals(review3._state.db, 'default')
+
+ # If you create an object through a M2M relation, it will be
+ # written to the write database, even if the original object
+ # was on the read database
+ dive = Book.objects.using('other').get(title='Dive into Python')
+ nyt = dive.reviews.create(source="New York Times", content_object=dive)
+ self.assertEquals(nyt._state.db, 'default')
+
+ def test_m2m_managers(self):
+ "M2M relations are represented by managers, and can be controlled like managers"
+ pro = Book.objects.using('other').create(pk=1, title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ marty = Person.objects.using('other').create(pk=1, name="Marty Alchin")
+ pro.authors = [marty]
+
+ self.assertEquals(pro.authors.db, 'other')
+ self.assertEquals(pro.authors.db_manager('default').db, 'default')
+ self.assertEquals(pro.authors.db_manager('default').all().db, 'default')
+
+ self.assertEquals(marty.book_set.db, 'other')
+ self.assertEquals(marty.book_set.db_manager('default').db, 'default')
+ self.assertEquals(marty.book_set.db_manager('default').all().db, 'default')
+
+ def test_foreign_key_managers(self):
+ "FK reverse relations are represented by managers, and can be controlled like managers"
+ marty = Person.objects.using('other').create(pk=1, name="Marty Alchin")
+ pro = Book.objects.using('other').create(pk=1, title="Pro Django",
+ published=datetime.date(2008, 12, 16),
+ editor=marty)
+
+ self.assertEquals(marty.edited.db, 'other')
+ self.assertEquals(marty.edited.db_manager('default').db, 'default')
+ self.assertEquals(marty.edited.db_manager('default').all().db, 'default')
+
+ def test_generic_key_managers(self):
+ "Generic key relations are represented by managers, and can be controlled like managers"
+ pro = Book.objects.using('other').create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
+
+ review1 = Review.objects.using('other').create(source="Python Monthly",
+ content_object=pro)
+
+ self.assertEquals(pro.reviews.db, 'other')
+ self.assertEquals(pro.reviews.db_manager('default').db, 'default')
+ self.assertEquals(pro.reviews.db_manager('default').all().db, 'default')
+
+ def test_subquery(self):
+ """Make sure as_sql works with subqueries and master/slave."""
+ # Create a book and author on the other database
+
+ mark = Person.objects.using('other').create(name="Mark Pilgrim")
+ dive = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4),
+ editor=mark)
+
+ sub = Person.objects.filter(name='Mark Pilgrim')
+ qs = Book.objects.filter(editor__in=sub)
+
+ # When you call __str__ on the query object, it doesn't know about using
+ # so it falls back to the default. Don't let routing instructions
+ # force the subquery to an incompatible database.
+ str(qs.query)
+
+ # If you evaluate the query, it should work, running on 'other'
+ self.assertEquals(list(qs.values_list('title', flat=True)), [u'Dive into Python'])
+
+class AuthTestCase(TestCase):
+ multi_db = True
+
+ def setUp(self):
+ # Make the 'other' database appear to be a slave of the 'default'
+ self.old_routers = router.routers
+ router.routers = [AuthRouter()]
+
+ def tearDown(self):
+ # Restore the 'other' database as an independent database
+ router.routers = self.old_routers
+
+ def test_auth_manager(self):
+ "The methods on the auth manager obey database hints"
+ # Create one user using default allocation policy
+ User.objects.create_user('alice', 'alice@example.com')
+
+ # Create another user, explicitly specifying the database
+ User.objects.db_manager('default').create_user('bob', 'bob@example.com')
+
+ # The second user only exists on the other database
+ alice = User.objects.using('other').get(username='alice')
+
+ self.assertEquals(alice.username, 'alice')
+ self.assertEquals(alice._state.db, 'other')
+
+ self.assertRaises(User.DoesNotExist, User.objects.using('default').get, username='alice')
+
+ # The second user only exists on the default database
+ bob = User.objects.using('default').get(username='bob')
+
+ self.assertEquals(bob.username, 'bob')
+ self.assertEquals(bob._state.db, 'default')
+
+ self.assertRaises(User.DoesNotExist, User.objects.using('other').get, username='bob')
+
+ # That is... there is one user on each database
+ self.assertEquals(User.objects.using('default').count(), 1)
+ self.assertEquals(User.objects.using('other').count(), 1)
+
+ def test_dumpdata(self):
+ "Check that dumpdata honors allow_syncdb restrictions on the router"
+ User.objects.create_user('alice', 'alice@example.com')
+ User.objects.db_manager('default').create_user('bob', 'bob@example.com')
+
+ # Check that dumping the default database doesn't try to include auth
+ # because allow_syncdb prohibits auth on default
+ new_io = StringIO()
+ management.call_command('dumpdata', 'auth', format='json', database='default', stdout=new_io)
+ command_output = new_io.getvalue().strip()
+ self.assertEqual(command_output, '[]')
+
+ # Check that dumping the other database does include auth
+ new_io = StringIO()
+ management.call_command('dumpdata', 'auth', format='json', database='other', stdout=new_io)
+ command_output = new_io.getvalue().strip()
+ self.assertTrue('"email": "alice@example.com",' in command_output)
+
+_missing = object()
+class UserProfileTestCase(TestCase):
+ def setUp(self):
+ self.old_auth_profile_module = getattr(settings, 'AUTH_PROFILE_MODULE', _missing)
+ settings.AUTH_PROFILE_MODULE = 'multiple_database.UserProfile'
+
+ def tearDown(self):
+ if self.old_auth_profile_module is _missing:
+ del settings.AUTH_PROFILE_MODULE
+ else:
+ settings.AUTH_PROFILE_MODULE = self.old_auth_profile_module
+
+ def test_user_profiles(self):
+
+ alice = User.objects.create_user('alice', 'alice@example.com')
+ bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
+
+ alice_profile = UserProfile(user=alice, flavor='chocolate')
+ alice_profile.save()
+
+ bob_profile = UserProfile(user=bob, flavor='crunchy frog')
+ bob_profile.save()
+
+ self.assertEquals(alice.get_profile().flavor, 'chocolate')
+ self.assertEquals(bob.get_profile().flavor, 'crunchy frog')
+
+class AntiPetRouter(object):
+ # A router that only expresses an opinion on syncdb,
+ # passing pets to the 'other' database
+
+ def allow_syncdb(self, db, model):
+ "Make sure the auth app only appears on the 'other' db"
+ if db == 'other':
+ return model._meta.object_name == 'Pet'
+ else:
+ return model._meta.object_name != 'Pet'
+ return None
+
+class FixtureTestCase(TestCase):
+ multi_db = True
+ fixtures = ['multidb-common', 'multidb']
+
+ def setUp(self):
+ # Install the anti-pet router
+ self.old_routers = router.routers
+ router.routers = [AntiPetRouter()]
+
+ def tearDown(self):
+ # Restore the 'other' database as an independent database
+ router.routers = self.old_routers
+
+ def test_fixture_loading(self):
+ "Multi-db fixtures are loaded correctly"
+ # Check that "Pro Django" exists on the default database, but not on other database
+ try:
+ Book.objects.get(title="Pro Django")
+ Book.objects.using('default').get(title="Pro Django")
+ except Book.DoesNotExist:
+ self.fail('"Pro Django" should exist on default database')
+
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.using('other').get,
+ title="Pro Django"
+ )
+
+ # Check that "Dive into Python" exists on the default database, but not on other database
+ try:
+ Book.objects.using('other').get(title="Dive into Python")
+ except Book.DoesNotExist:
+ self.fail('"Dive into Python" should exist on other database')
+
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.get,
+ title="Dive into Python"
+ )
+ self.assertRaises(Book.DoesNotExist,
+ Book.objects.using('default').get,
+ title="Dive into Python"
+ )
+
+ # Check that "Definitive Guide" exists on the both databases
+ try:
+ Book.objects.get(title="The Definitive Guide to Django")
+ Book.objects.using('default').get(title="The Definitive Guide to Django")
+ Book.objects.using('other').get(title="The Definitive Guide to Django")
+ except Book.DoesNotExist:
+ self.fail('"The Definitive Guide to Django" should exist on both databases')
+
+ def test_pseudo_empty_fixtures(self):
+ "A fixture can contain entries, but lead to nothing in the database; this shouldn't raise an error (ref #14068)"
+ new_io = StringIO()
+ management.call_command('loaddata', 'pets', stdout=new_io, stderr=new_io)
+ command_output = new_io.getvalue().strip()
+ # No objects will actually be loaded
+ self.assertTrue("Installed 0 object(s) (of 2) from 1 fixture(s)" in command_output)
+
+class PickleQuerySetTestCase(TestCase):
+ multi_db = True
+
+ def test_pickling(self):
+ for db in connections:
+ Book.objects.using(db).create(title='Dive into Python', published=datetime.date(2009, 5, 4))
+ qs = Book.objects.all()
+ self.assertEqual(qs.db, pickle.loads(pickle.dumps(qs)).db)
diff --git a/parts/django/tests/regressiontests/null_fk/__init__.py b/parts/django/tests/regressiontests/null_fk/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_fk/__init__.py
diff --git a/parts/django/tests/regressiontests/null_fk/models.py b/parts/django/tests/regressiontests/null_fk/models.py
new file mode 100644
index 0000000..3cce319
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_fk/models.py
@@ -0,0 +1,33 @@
+"""
+Regression tests for proper working of ForeignKey(null=True).
+"""
+
+from django.db import models
+
+class SystemDetails(models.Model):
+ details = models.TextField()
+
+class SystemInfo(models.Model):
+ system_details = models.ForeignKey(SystemDetails)
+ system_name = models.CharField(max_length=32)
+
+class Forum(models.Model):
+ system_info = models.ForeignKey(SystemInfo)
+ forum_name = models.CharField(max_length=32)
+
+class Post(models.Model):
+ forum = models.ForeignKey(Forum, null=True)
+ title = models.CharField(max_length=32)
+
+ def __unicode__(self):
+ return self.title
+
+class Comment(models.Model):
+ post = models.ForeignKey(Post, null=True)
+ comment_text = models.CharField(max_length=250)
+
+ class Meta:
+ ordering = ('comment_text',)
+
+ def __unicode__(self):
+ return self.comment_text
diff --git a/parts/django/tests/regressiontests/null_fk/tests.py b/parts/django/tests/regressiontests/null_fk/tests.py
new file mode 100644
index 0000000..449f343
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_fk/tests.py
@@ -0,0 +1,42 @@
+from django.test import TestCase
+
+from regressiontests.null_fk.models import *
+
+class NullFkTests(TestCase):
+
+ def test_null_fk(self):
+ d = SystemDetails.objects.create(details='First details')
+ s = SystemInfo.objects.create(system_name='First forum', system_details=d)
+ f = Forum.objects.create(system_info=s, forum_name='First forum')
+ p = Post.objects.create(forum=f, title='First Post')
+ c1 = Comment.objects.create(post=p, comment_text='My first comment')
+ c2 = Comment.objects.create(comment_text='My second comment')
+
+ # Starting from comment, make sure that a .select_related(...) with a specified
+ # set of fields will properly LEFT JOIN multiple levels of NULLs (and the things
+ # that come after the NULLs, or else data that should exist won't). Regression
+ # test for #7369.
+ c = Comment.objects.select_related().get(id=1)
+ self.assertEquals(c.post, p)
+ self.assertEquals(Comment.objects.select_related().get(id=2).post, None)
+
+ self.assertQuerysetEqual(
+ Comment.objects.select_related('post__forum__system_info').all(),
+ [
+ (1, u'My first comment', '<Post: First Post>'),
+ (2, u'My second comment', 'None')
+ ],
+ transform = lambda c: (c.id, c.comment_text, repr(c.post))
+ )
+
+ # Regression test for #7530, #7716.
+ self.assertTrue(Comment.objects.select_related('post').filter(post__isnull=True)[0].post is None)
+
+ self.assertQuerysetEqual(
+ Comment.objects.select_related('post__forum__system_info__system_details'),
+ [
+ (1, u'My first comment', '<Post: First Post>'),
+ (2, u'My second comment', 'None')
+ ],
+ transform = lambda c: (c.id, c.comment_text, repr(c.post))
+ )
diff --git a/parts/django/tests/regressiontests/null_fk_ordering/__init__.py b/parts/django/tests/regressiontests/null_fk_ordering/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_fk_ordering/__init__.py
diff --git a/parts/django/tests/regressiontests/null_fk_ordering/models.py b/parts/django/tests/regressiontests/null_fk_ordering/models.py
new file mode 100644
index 0000000..d0635e8
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_fk_ordering/models.py
@@ -0,0 +1,49 @@
+"""
+Regression tests for proper working of ForeignKey(null=True). Tests these bugs:
+
+ * #7512: including a nullable foreign key reference in Meta ordering has un
+xpected results
+
+"""
+
+from django.db import models
+
+# The first two models represent a very simple null FK ordering case.
+class Author(models.Model):
+ name = models.CharField(max_length=150)
+
+class Article(models.Model):
+ title = models.CharField(max_length=150)
+ author = models.ForeignKey(Author, null=True)
+
+ def __unicode__(self):
+ return u'Article titled: %s' % (self.title, )
+
+ class Meta:
+ ordering = ['author__name', ]
+
+
+# These following 4 models represent a far more complex ordering case.
+class SystemInfo(models.Model):
+ system_name = models.CharField(max_length=32)
+
+class Forum(models.Model):
+ system_info = models.ForeignKey(SystemInfo)
+ forum_name = models.CharField(max_length=32)
+
+class Post(models.Model):
+ forum = models.ForeignKey(Forum, null=True)
+ title = models.CharField(max_length=32)
+
+ def __unicode__(self):
+ return self.title
+
+class Comment(models.Model):
+ post = models.ForeignKey(Post, null=True)
+ comment_text = models.CharField(max_length=250)
+
+ class Meta:
+ ordering = ['post__forum__system_info__system_name', 'comment_text']
+
+ def __unicode__(self):
+ return self.comment_text
diff --git a/parts/django/tests/regressiontests/null_fk_ordering/tests.py b/parts/django/tests/regressiontests/null_fk_ordering/tests.py
new file mode 100644
index 0000000..c9ee4f7
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_fk_ordering/tests.py
@@ -0,0 +1,39 @@
+from django.test import TestCase
+
+from regressiontests.null_fk_ordering.models import *
+
+class NullFkOrderingTests(TestCase):
+
+ def test_ordering_across_null_fk(self):
+ """
+ Regression test for #7512
+
+ ordering across nullable Foreign Keys shouldn't exclude results
+ """
+ author_1 = Author.objects.create(name='Tom Jones')
+ author_2 = Author.objects.create(name='Bob Smith')
+ article_1 = Article.objects.create(title='No author on this article')
+ article_2 = Article.objects.create(author=author_1, title='This article written by Tom Jones')
+ article_3 = Article.objects.create(author=author_2, title='This article written by Bob Smith')
+
+ # We can't compare results directly (since different databases sort NULLs to
+ # different ends of the ordering), but we can check that all results are
+ # returned.
+ self.assertTrue(len(list(Article.objects.all())) == 3)
+
+ s = SystemInfo.objects.create(system_name='System Info')
+ f = Forum.objects.create(system_info=s, forum_name='First forum')
+ p = Post.objects.create(forum=f, title='First Post')
+ c1 = Comment.objects.create(post=p, comment_text='My first comment')
+ c2 = Comment.objects.create(comment_text='My second comment')
+ s2 = SystemInfo.objects.create(system_name='More System Info')
+ f2 = Forum.objects.create(system_info=s2, forum_name='Second forum')
+ p2 = Post.objects.create(forum=f2, title='Second Post')
+ c3 = Comment.objects.create(comment_text='Another first comment')
+ c4 = Comment.objects.create(post=p2, comment_text='Another second comment')
+
+ # We have to test this carefully. Some databases sort NULL values before
+ # everything else, some sort them afterwards. So we extract the ordered list
+ # and check the length. Before the fix, this list was too short (some values
+ # were omitted).
+ self.assertTrue(len(list(Comment.objects.all())) == 4)
diff --git a/parts/django/tests/regressiontests/null_queries/__init__.py b/parts/django/tests/regressiontests/null_queries/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_queries/__init__.py
diff --git a/parts/django/tests/regressiontests/null_queries/models.py b/parts/django/tests/regressiontests/null_queries/models.py
new file mode 100644
index 0000000..442535c
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_queries/models.py
@@ -0,0 +1,25 @@
+from django.db import models
+
+class Poll(models.Model):
+ question = models.CharField(max_length=200)
+
+ def __unicode__(self):
+ return u"Q: %s " % self.question
+
+class Choice(models.Model):
+ poll = models.ForeignKey(Poll)
+ choice = models.CharField(max_length=200)
+
+ def __unicode__(self):
+ return u"Choice: %s in poll %s" % (self.choice, self.poll)
+
+# A set of models with an inner one pointing to two outer ones.
+class OuterA(models.Model):
+ pass
+
+class OuterB(models.Model):
+ data = models.CharField(max_length=10)
+
+class Inner(models.Model):
+ first = models.ForeignKey(OuterA)
+ second = models.ForeignKey(OuterB, null=True)
diff --git a/parts/django/tests/regressiontests/null_queries/tests.py b/parts/django/tests/regressiontests/null_queries/tests.py
new file mode 100644
index 0000000..72dcd51
--- /dev/null
+++ b/parts/django/tests/regressiontests/null_queries/tests.py
@@ -0,0 +1,69 @@
+from django.test import TestCase
+from django.core.exceptions import FieldError
+
+from regressiontests.null_queries.models import *
+
+
+class NullQueriesTests(TestCase):
+
+ def test_none_as_null(self):
+ """
+ Regression test for the use of None as a query value.
+
+ None is interpreted as an SQL NULL, but only in __exact queries.
+ Set up some initial polls and choices
+ """
+ p1 = Poll(question='Why?')
+ p1.save()
+ c1 = Choice(poll=p1, choice='Because.')
+ c1.save()
+ c2 = Choice(poll=p1, choice='Why Not?')
+ c2.save()
+
+ # Exact query with value None returns nothing ("is NULL" in sql,
+ # but every 'id' field has a value).
+ self.assertQuerysetEqual(Choice.objects.filter(choice__exact=None), [])
+
+ # Excluding the previous result returns everything.
+ self.assertQuerysetEqual(
+ Choice.objects.exclude(choice=None).order_by('id'),
+ [
+ '<Choice: Choice: Because. in poll Q: Why? >',
+ '<Choice: Choice: Why Not? in poll Q: Why? >'
+ ]
+ )
+
+ # Valid query, but fails because foo isn't a keyword
+ self.assertRaises(FieldError, Choice.objects.filter, foo__exact=None)
+
+ # Can't use None on anything other than __exact
+ self.assertRaises(ValueError, Choice.objects.filter, id__gt=None)
+
+ # Can't use None on anything other than __exact
+ self.assertRaises(ValueError, Choice.objects.filter, foo__gt=None)
+
+ # Related managers use __exact=None implicitly if the object hasn't been saved.
+ p2 = Poll(question="How?")
+ self.assertEquals(repr(p2.choice_set.all()), '[]')
+
+ def test_reverse_relations(self):
+ """
+ Querying across reverse relations and then another relation should
+ insert outer joins correctly so as not to exclude results.
+ """
+ obj = OuterA.objects.create()
+ self.assertQuerysetEqual(
+ OuterA.objects.filter(inner__second=None),
+ ['<OuterA: OuterA object>']
+ )
+ self.assertQuerysetEqual(
+ OuterA.objects.filter(inner__second__data=None),
+ ['<OuterA: OuterA object>']
+ )
+
+ inner_obj = Inner.objects.create(first=obj)
+ self.assertQuerysetEqual(
+ Inner.objects.filter(first__inner__second=None),
+ ['<Inner: Inner object>']
+ )
+
diff --git a/parts/django/tests/regressiontests/one_to_one_regress/__init__.py b/parts/django/tests/regressiontests/one_to_one_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/one_to_one_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/one_to_one_regress/models.py b/parts/django/tests/regressiontests/one_to_one_regress/models.py
new file mode 100644
index 0000000..a7edbc0
--- /dev/null
+++ b/parts/django/tests/regressiontests/one_to_one_regress/models.py
@@ -0,0 +1,43 @@
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+ address = models.CharField(max_length=80)
+
+ def __unicode__(self):
+ return u"%s the place" % self.name
+
+class Restaurant(models.Model):
+ place = models.OneToOneField(Place)
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the restaurant" % self.place.name
+
+class Bar(models.Model):
+ place = models.OneToOneField(Place)
+ serves_cocktails = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the bar" % self.place.name
+
+class UndergroundBar(models.Model):
+ place = models.OneToOneField(Place, null=True)
+ serves_cocktails = models.BooleanField()
+
+class Favorites(models.Model):
+ name = models.CharField(max_length = 50)
+ restaurants = models.ManyToManyField(Restaurant)
+
+ def __unicode__(self):
+ return u"Favorites for %s" % self.name
+
+class Target(models.Model):
+ pass
+
+class Pointer(models.Model):
+ other = models.OneToOneField(Target, primary_key=True)
+
+class Pointer2(models.Model):
+ other = models.OneToOneField(Target)
diff --git a/parts/django/tests/regressiontests/one_to_one_regress/tests.py b/parts/django/tests/regressiontests/one_to_one_regress/tests.py
new file mode 100644
index 0000000..8787575
--- /dev/null
+++ b/parts/django/tests/regressiontests/one_to_one_regress/tests.py
@@ -0,0 +1,130 @@
+from django.test import TestCase
+from regressiontests.one_to_one_regress.models import *
+
+class OneToOneRegressionTests(TestCase):
+
+ def setUp(self):
+ self.p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
+ self.p1.save()
+ self.r1 = Restaurant(place=self.p1, serves_hot_dogs=True, serves_pizza=False)
+ self.r1.save()
+ self.b1 = Bar(place=self.p1, serves_cocktails=False)
+ self.b1.save()
+
+ def test_reverse_relationship_cache_cascade(self):
+ """
+ Regression test for #9023: accessing the reverse relationship shouldn't
+ result in a cascading delete().
+ """
+ bar = UndergroundBar.objects.create(place=self.p1, serves_cocktails=False)
+
+ # The bug in #9023: if you access the one-to-one relation *before*
+ # setting to None and deleting, the cascade happens anyway.
+ self.p1.undergroundbar
+ bar.place.name='foo'
+ bar.place = None
+ bar.save()
+ self.p1.delete()
+
+ self.assertEqual(Place.objects.all().count(), 0)
+ self.assertEqual(UndergroundBar.objects.all().count(), 1)
+
+ def test_create_models_m2m(self):
+ """
+ Regression test for #1064 and #1506
+
+ Check that we create models via the m2m relation if the remote model
+ has a OneToOneField.
+ """
+ f = Favorites(name = 'Fred')
+ f.save()
+ f.restaurants = [self.r1]
+ self.assertQuerysetEqual(
+ f.restaurants.all(),
+ ['<Restaurant: Demon Dogs the restaurant>']
+ )
+
+ def test_reverse_object_cache(self):
+ """
+ Regression test for #7173
+
+ Check that the name of the cache for the reverse object is correct.
+ """
+ self.assertEquals(self.p1.restaurant, self.r1)
+ self.assertEquals(self.p1.bar, self.b1)
+
+ def test_related_object_cache(self):
+ """ Regression test for #6886 (the related-object cache) """
+
+ # Look up the objects again so that we get "fresh" objects
+ p = Place.objects.get(name="Demon Dogs")
+ r = p.restaurant
+
+ # Accessing the related object again returns the exactly same object
+ self.assertTrue(p.restaurant is r)
+
+ # But if we kill the cache, we get a new object
+ del p._restaurant_cache
+ self.assertFalse(p.restaurant is r)
+
+ # Reassigning the Restaurant object results in an immediate cache update
+ # We can't use a new Restaurant because that'll violate one-to-one, but
+ # with a new *instance* the is test below will fail if #6886 regresses.
+ r2 = Restaurant.objects.get(pk=r.pk)
+ p.restaurant = r2
+ self.assertTrue(p.restaurant is r2)
+
+ # Assigning None succeeds if field is null=True.
+ ug_bar = UndergroundBar.objects.create(place=p, serves_cocktails=False)
+ ug_bar.place = None
+ self.assertTrue(ug_bar.place is None)
+
+ # Assigning None fails: Place.restaurant is null=False
+ self.assertRaises(ValueError, setattr, p, 'restaurant', None)
+
+ # You also can't assign an object of the wrong type here
+ self.assertRaises(ValueError, setattr, p, 'restaurant', p)
+
+ # Creation using keyword argument should cache the related object.
+ p = Place.objects.get(name="Demon Dogs")
+ r = Restaurant(place=p)
+ self.assertTrue(r.place is p)
+
+ # Creation using keyword argument and unsaved related instance (#8070).
+ p = Place()
+ r = Restaurant(place=p)
+ self.assertTrue(r.place is p)
+
+ # Creation using attname keyword argument and an id will cause the related
+ # object to be fetched.
+ p = Place.objects.get(name="Demon Dogs")
+ r = Restaurant(place_id=p.id)
+ self.assertFalse(r.place is p)
+ self.assertEqual(r.place, p)
+
+ def test_filter_one_to_one_relations(self):
+ """
+ Regression test for #9968
+
+ filtering reverse one-to-one relations with primary_key=True was
+ misbehaving. We test both (primary_key=True & False) cases here to
+ prevent any reappearance of the problem.
+ """
+ t = Target.objects.create()
+
+ self.assertQuerysetEqual(
+ Target.objects.filter(pointer=None),
+ ['<Target: Target object>']
+ )
+ self.assertQuerysetEqual(
+ Target.objects.exclude(pointer=None),
+ []
+ )
+ self.assertQuerysetEqual(
+ Target.objects.filter(pointer2=None),
+ ['<Target: Target object>']
+ )
+ self.assertQuerysetEqual(
+ Target.objects.exclude(pointer2=None),
+ []
+ )
diff --git a/parts/django/tests/regressiontests/pagination_regress/__init__.py b/parts/django/tests/regressiontests/pagination_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/pagination_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/pagination_regress/models.py b/parts/django/tests/regressiontests/pagination_regress/models.py
new file mode 100644
index 0000000..cde172d
--- /dev/null
+++ b/parts/django/tests/regressiontests/pagination_regress/models.py
@@ -0,0 +1 @@
+# Models file for tests to run.
diff --git a/parts/django/tests/regressiontests/pagination_regress/tests.py b/parts/django/tests/regressiontests/pagination_regress/tests.py
new file mode 100644
index 0000000..08436df
--- /dev/null
+++ b/parts/django/tests/regressiontests/pagination_regress/tests.py
@@ -0,0 +1,157 @@
+from unittest import TestCase
+
+from django.core.paginator import Paginator, EmptyPage
+
+class PaginatorTests(TestCase):
+ """
+ Tests for the Paginator and Page classes.
+ """
+
+ def check_paginator(self, params, output):
+ """
+ Helper method that instantiates a Paginator object from the passed
+ params and then checks that its attributes match the passed output.
+ """
+ count, num_pages, page_range = output
+ paginator = Paginator(*params)
+ self.check_attribute('count', paginator, count, params)
+ self.check_attribute('num_pages', paginator, num_pages, params)
+ self.check_attribute('page_range', paginator, page_range, params)
+
+ def check_attribute(self, name, paginator, expected, params):
+ """
+ Helper method that checks a single attribute and gives a nice error
+ message upon test failure.
+ """
+ got = getattr(paginator, name)
+ self.assertEqual(expected, got,
+ "For '%s', expected %s but got %s. Paginator parameters were: %s"
+ % (name, expected, got, params))
+
+ def test_paginator(self):
+ """
+ Tests the paginator attributes using varying inputs.
+ """
+ nine = [1, 2, 3, 4, 5, 6, 7, 8, 9]
+ ten = nine + [10]
+ eleven = ten + [11]
+ tests = (
+ # Each item is two tuples:
+ # First tuple is Paginator parameters - object_list, per_page,
+ # orphans, and allow_empty_first_page.
+ # Second tuple is resulting Paginator attributes - count,
+ # num_pages, and page_range.
+ # Ten items, varying orphans, no empty first page.
+ ((ten, 4, 0, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, 1, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, 2, False), (10, 2, [1, 2])),
+ ((ten, 4, 5, False), (10, 2, [1, 2])),
+ ((ten, 4, 6, False), (10, 1, [1])),
+ # Ten items, varying orphans, allow empty first page.
+ ((ten, 4, 0, True), (10, 3, [1, 2, 3])),
+ ((ten, 4, 1, True), (10, 3, [1, 2, 3])),
+ ((ten, 4, 2, True), (10, 2, [1, 2])),
+ ((ten, 4, 5, True), (10, 2, [1, 2])),
+ ((ten, 4, 6, True), (10, 1, [1])),
+ # One item, varying orphans, no empty first page.
+ (([1], 4, 0, False), (1, 1, [1])),
+ (([1], 4, 1, False), (1, 1, [1])),
+ (([1], 4, 2, False), (1, 1, [1])),
+ # One item, varying orphans, allow empty first page.
+ (([1], 4, 0, True), (1, 1, [1])),
+ (([1], 4, 1, True), (1, 1, [1])),
+ (([1], 4, 2, True), (1, 1, [1])),
+ # Zero items, varying orphans, no empty first page.
+ (([], 4, 0, False), (0, 0, [])),
+ (([], 4, 1, False), (0, 0, [])),
+ (([], 4, 2, False), (0, 0, [])),
+ # Zero items, varying orphans, allow empty first page.
+ (([], 4, 0, True), (0, 1, [1])),
+ (([], 4, 1, True), (0, 1, [1])),
+ (([], 4, 2, True), (0, 1, [1])),
+ # Number if items one less than per_page.
+ (([], 1, 0, True), (0, 1, [1])),
+ (([], 1, 0, False), (0, 0, [])),
+ (([1], 2, 0, True), (1, 1, [1])),
+ ((nine, 10, 0, True), (9, 1, [1])),
+ # Number if items equal to per_page.
+ (([1], 1, 0, True), (1, 1, [1])),
+ (([1, 2], 2, 0, True), (2, 1, [1])),
+ ((ten, 10, 0, True), (10, 1, [1])),
+ # Number if items one more than per_page.
+ (([1, 2], 1, 0, True), (2, 2, [1, 2])),
+ (([1, 2, 3], 2, 0, True), (3, 2, [1, 2])),
+ ((eleven, 10, 0, True), (11, 2, [1, 2])),
+ # Number if items one more than per_page with one orphan.
+ (([1, 2], 1, 1, True), (2, 1, [1])),
+ (([1, 2, 3], 2, 1, True), (3, 1, [1])),
+ ((eleven, 10, 1, True), (11, 1, [1])),
+ )
+ for params, output in tests:
+ self.check_paginator(params, output)
+
+ def check_indexes(self, params, page_num, indexes):
+ """
+ Helper method that instantiates a Paginator object from the passed
+ params and then checks that the start and end indexes of the passed
+ page_num match those given as a 2-tuple in indexes.
+ """
+ paginator = Paginator(*params)
+ if page_num == 'first':
+ page_num = 1
+ elif page_num == 'last':
+ page_num = paginator.num_pages
+ page = paginator.page(page_num)
+ start, end = indexes
+ msg = ("For %s of page %s, expected %s but got %s."
+ " Paginator parameters were: %s")
+ self.assertEqual(start, page.start_index(),
+ msg % ('start index', page_num, start, page.start_index(), params))
+ self.assertEqual(end, page.end_index(),
+ msg % ('end index', page_num, end, page.end_index(), params))
+
+ def test_page_indexes(self):
+ """
+ Tests that paginator pages have the correct start and end indexes.
+ """
+ ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tests = (
+ # Each item is three tuples:
+ # First tuple is Paginator parameters - object_list, per_page,
+ # orphans, and allow_empty_first_page.
+ # Second tuple is the start and end indexes of the first page.
+ # Third tuple is the start and end indexes of the last page.
+ # Ten items, varying per_page, no orphans.
+ ((ten, 1, 0, True), (1, 1), (10, 10)),
+ ((ten, 2, 0, True), (1, 2), (9, 10)),
+ ((ten, 3, 0, True), (1, 3), (10, 10)),
+ ((ten, 5, 0, True), (1, 5), (6, 10)),
+ # Ten items, varying per_page, with orphans.
+ ((ten, 1, 1, True), (1, 1), (9, 10)),
+ ((ten, 1, 2, True), (1, 1), (8, 10)),
+ ((ten, 3, 1, True), (1, 3), (7, 10)),
+ ((ten, 3, 2, True), (1, 3), (7, 10)),
+ ((ten, 3, 4, True), (1, 3), (4, 10)),
+ ((ten, 5, 1, True), (1, 5), (6, 10)),
+ ((ten, 5, 2, True), (1, 5), (6, 10)),
+ ((ten, 5, 5, True), (1, 10), (1, 10)),
+ # One item, varying orphans, no empty first page.
+ (([1], 4, 0, False), (1, 1), (1, 1)),
+ (([1], 4, 1, False), (1, 1), (1, 1)),
+ (([1], 4, 2, False), (1, 1), (1, 1)),
+ # One item, varying orphans, allow empty first page.
+ (([1], 4, 0, True), (1, 1), (1, 1)),
+ (([1], 4, 1, True), (1, 1), (1, 1)),
+ (([1], 4, 2, True), (1, 1), (1, 1)),
+ # Zero items, varying orphans, allow empty first page.
+ (([], 4, 0, True), (0, 0), (0, 0)),
+ (([], 4, 1, True), (0, 0), (0, 0)),
+ (([], 4, 2, True), (0, 0), (0, 0)),
+ )
+ for params, first, last in tests:
+ self.check_indexes(params, 'first', first)
+ self.check_indexes(params, 'last', last)
+ # When no items and no empty first page, we should get EmptyPage error.
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 0, False), 1, None)
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 1, False), 1, None)
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 2, False), 1, None)
diff --git a/parts/django/tests/regressiontests/queries/__init__.py b/parts/django/tests/regressiontests/queries/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/queries/__init__.py
diff --git a/parts/django/tests/regressiontests/queries/models.py b/parts/django/tests/regressiontests/queries/models.py
new file mode 100644
index 0000000..5247ef9
--- /dev/null
+++ b/parts/django/tests/regressiontests/queries/models.py
@@ -0,0 +1,276 @@
+"""
+Various complex queries that have been problematic in the past.
+"""
+
+import threading
+
+from django.db import models
+
+class DumbCategory(models.Model):
+ pass
+
+class NamedCategory(DumbCategory):
+ name = models.CharField(max_length=10)
+
+class Tag(models.Model):
+ name = models.CharField(max_length=10)
+ parent = models.ForeignKey('self', blank=True, null=True,
+ related_name='children')
+ category = models.ForeignKey(NamedCategory, null=True, default=None)
+
+ class Meta:
+ ordering = ['name']
+
+ def __unicode__(self):
+ return self.name
+
+class Note(models.Model):
+ note = models.CharField(max_length=100)
+ misc = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ['note']
+
+ def __unicode__(self):
+ return self.note
+
+ def __init__(self, *args, **kwargs):
+ super(Note, self).__init__(*args, **kwargs)
+ # Regression for #13227 -- having an attribute that
+ # is unpickleable doesn't stop you from cloning queries
+ # that use objects of that type as an argument.
+ self.lock = threading.Lock()
+
+class Annotation(models.Model):
+ name = models.CharField(max_length=10)
+ tag = models.ForeignKey(Tag)
+ notes = models.ManyToManyField(Note)
+
+ def __unicode__(self):
+ return self.name
+
+class ExtraInfo(models.Model):
+ info = models.CharField(max_length=100)
+ note = models.ForeignKey(Note)
+
+ class Meta:
+ ordering = ['info']
+
+ def __unicode__(self):
+ return self.info
+
+class Author(models.Model):
+ name = models.CharField(max_length=10)
+ num = models.IntegerField(unique=True)
+ extra = models.ForeignKey(ExtraInfo)
+
+ class Meta:
+ ordering = ['name']
+
+ def __unicode__(self):
+ return self.name
+
+class Item(models.Model):
+ name = models.CharField(max_length=10)
+ created = models.DateTimeField()
+ modified = models.DateTimeField(blank=True, null=True)
+ tags = models.ManyToManyField(Tag, blank=True, null=True)
+ creator = models.ForeignKey(Author)
+ note = models.ForeignKey(Note)
+
+ class Meta:
+ ordering = ['-note', 'name']
+
+ def __unicode__(self):
+ return self.name
+
+class Report(models.Model):
+ name = models.CharField(max_length=10)
+ creator = models.ForeignKey(Author, to_field='num', null=True)
+
+ def __unicode__(self):
+ return self.name
+
+class Ranking(models.Model):
+ rank = models.IntegerField()
+ author = models.ForeignKey(Author)
+
+ class Meta:
+ # A complex ordering specification. Should stress the system a bit.
+ ordering = ('author__extra__note', 'author__name', 'rank')
+
+ def __unicode__(self):
+ return '%d: %s' % (self.rank, self.author.name)
+
+class Cover(models.Model):
+ title = models.CharField(max_length=50)
+ item = models.ForeignKey(Item)
+
+ class Meta:
+ ordering = ['item']
+
+ def __unicode__(self):
+ return self.title
+
+class Number(models.Model):
+ num = models.IntegerField()
+
+ def __unicode__(self):
+ return unicode(self.num)
+
+# Symmetrical m2m field with a normal field using the reverse accesor name
+# ("valid").
+class Valid(models.Model):
+ valid = models.CharField(max_length=10)
+ parent = models.ManyToManyField('self')
+
+ class Meta:
+ ordering = ['valid']
+
+# Some funky cross-linked models for testing a couple of infinite recursion
+# cases.
+class X(models.Model):
+ y = models.ForeignKey('Y')
+
+class Y(models.Model):
+ x1 = models.ForeignKey(X, related_name='y1')
+
+# Some models with a cycle in the default ordering. This would be bad if we
+# didn't catch the infinite loop.
+class LoopX(models.Model):
+ y = models.ForeignKey('LoopY')
+
+ class Meta:
+ ordering = ['y']
+
+class LoopY(models.Model):
+ x = models.ForeignKey(LoopX)
+
+ class Meta:
+ ordering = ['x']
+
+class LoopZ(models.Model):
+ z = models.ForeignKey('self')
+
+ class Meta:
+ ordering = ['z']
+
+# A model and custom default manager combination.
+class CustomManager(models.Manager):
+ def get_query_set(self):
+ qs = super(CustomManager, self).get_query_set()
+ return qs.filter(public=True, tag__name='t1')
+
+class ManagedModel(models.Model):
+ data = models.CharField(max_length=10)
+ tag = models.ForeignKey(Tag)
+ public = models.BooleanField(default=True)
+
+ objects = CustomManager()
+ normal_manager = models.Manager()
+
+ def __unicode__(self):
+ return self.data
+
+# An inter-related setup with multiple paths from Child to Detail.
+class Detail(models.Model):
+ data = models.CharField(max_length=10)
+
+class MemberManager(models.Manager):
+ def get_query_set(self):
+ return super(MemberManager, self).get_query_set().select_related("details")
+
+class Member(models.Model):
+ name = models.CharField(max_length=10)
+ details = models.OneToOneField(Detail, primary_key=True)
+
+ objects = MemberManager()
+
+class Child(models.Model):
+ person = models.OneToOneField(Member, primary_key=True)
+ parent = models.ForeignKey(Member, related_name="children")
+
+# Custom primary keys interfered with ordering in the past.
+class CustomPk(models.Model):
+ name = models.CharField(max_length=10, primary_key=True)
+ extra = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ['name', 'extra']
+
+class Related(models.Model):
+ custom = models.ForeignKey(CustomPk)
+
+# An inter-related setup with a model subclass that has a nullable
+# path to another model, and a return path from that model.
+
+class Celebrity(models.Model):
+ name = models.CharField("Name", max_length=20)
+ greatest_fan = models.ForeignKey("Fan", null=True, unique=True)
+
+class TvChef(Celebrity):
+ pass
+
+class Fan(models.Model):
+ fan_of = models.ForeignKey(Celebrity)
+
+# Multiple foreign keys
+class LeafA(models.Model):
+ data = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return self.data
+
+class LeafB(models.Model):
+ data = models.CharField(max_length=10)
+
+class Join(models.Model):
+ a = models.ForeignKey(LeafA)
+ b = models.ForeignKey(LeafB)
+
+class ReservedName(models.Model):
+ name = models.CharField(max_length=20)
+ order = models.IntegerField()
+
+ def __unicode__(self):
+ return self.name
+
+# A simpler shared-foreign-key setup that can expose some problems.
+class SharedConnection(models.Model):
+ data = models.CharField(max_length=10)
+
+class PointerA(models.Model):
+ connection = models.ForeignKey(SharedConnection)
+
+class PointerB(models.Model):
+ connection = models.ForeignKey(SharedConnection)
+
+# Multi-layer ordering
+class SingleObject(models.Model):
+ name = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ['name']
+
+ def __unicode__(self):
+ return self.name
+
+class RelatedObject(models.Model):
+ single = models.ForeignKey(SingleObject)
+
+ class Meta:
+ ordering = ['single']
+
+class Plaything(models.Model):
+ name = models.CharField(max_length=10)
+ others = models.ForeignKey(RelatedObject, null=True)
+
+ class Meta:
+ ordering = ['others']
+
+ def __unicode__(self):
+ return self.name
+
+class Article(models.Model):
+ name = models.CharField(max_length=20)
+ created = models.DateTimeField()
diff --git a/parts/django/tests/regressiontests/queries/tests.py b/parts/django/tests/regressiontests/queries/tests.py
new file mode 100644
index 0000000..741b33c
--- /dev/null
+++ b/parts/django/tests/regressiontests/queries/tests.py
@@ -0,0 +1,1586 @@
+import datetime
+import pickle
+import sys
+import unittest
+
+from django.conf import settings
+from django.core.exceptions import FieldError
+from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS
+from django.db.models import Count
+from django.db.models.query import Q, ITER_CHUNK_SIZE, EmptyQuerySet
+from django.test import TestCase
+from django.utils.datastructures import SortedDict
+
+from models import (Annotation, Article, Author, Celebrity, Child, Cover, Detail,
+ DumbCategory, ExtraInfo, Fan, Item, LeafA, LoopX, LoopZ, ManagedModel,
+ Member, NamedCategory, Note, Number, Plaything, PointerA, Ranking, Related,
+ Report, ReservedName, Tag, TvChef, Valid, X)
+
+
+class BaseQuerysetTest(TestCase):
+ def assertValueQuerysetEqual(self, qs, values):
+ return self.assertQuerysetEqual(qs, values, transform=lambda x: x)
+
+ def assertRaisesMessage(self, exc, msg, func, *args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except Exception, e:
+ self.assertEqual(msg, str(e))
+ self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e)))
+ else:
+ if hasattr(exc, '__name__'):
+ excName = exc.__name__
+ else:
+ excName = str(exc)
+ raise AssertionError, "%s not raised" % excName
+
+
+class Queries1Tests(BaseQuerysetTest):
+ def setUp(self):
+ generic = NamedCategory.objects.create(name="Generic")
+ self.t1 = Tag.objects.create(name='t1', category=generic)
+ self.t2 = Tag.objects.create(name='t2', parent=self.t1, category=generic)
+ self.t3 = Tag.objects.create(name='t3', parent=self.t1)
+ t4 = Tag.objects.create(name='t4', parent=self.t3)
+ self.t5 = Tag.objects.create(name='t5', parent=self.t3)
+
+ self.n1 = Note.objects.create(note='n1', misc='foo', id=1)
+ n2 = Note.objects.create(note='n2', misc='bar', id=2)
+ self.n3 = Note.objects.create(note='n3', misc='foo', id=3)
+
+ ann1 = Annotation.objects.create(name='a1', tag=self.t1)
+ ann1.notes.add(self.n1)
+ ann2 = Annotation.objects.create(name='a2', tag=t4)
+ ann2.notes.add(n2, self.n3)
+
+ # Create these out of order so that sorting by 'id' will be different to sorting
+ # by 'info'. Helps detect some problems later.
+ self.e2 = ExtraInfo.objects.create(info='e2', note=n2)
+ e1 = ExtraInfo.objects.create(info='e1', note=self.n1)
+
+ self.a1 = Author.objects.create(name='a1', num=1001, extra=e1)
+ self.a2 = Author.objects.create(name='a2', num=2002, extra=e1)
+ a3 = Author.objects.create(name='a3', num=3003, extra=self.e2)
+ self.a4 = Author.objects.create(name='a4', num=4004, extra=self.e2)
+
+ self.time1 = datetime.datetime(2007, 12, 19, 22, 25, 0)
+ self.time2 = datetime.datetime(2007, 12, 19, 21, 0, 0)
+ time3 = datetime.datetime(2007, 12, 20, 22, 25, 0)
+ time4 = datetime.datetime(2007, 12, 20, 21, 0, 0)
+ self.i1 = Item.objects.create(name='one', created=self.time1, modified=self.time1, creator=self.a1, note=self.n3)
+ self.i1.tags = [self.t1, self.t2]
+ self.i2 = Item.objects.create(name='two', created=self.time2, creator=self.a2, note=n2)
+ self.i2.tags = [self.t1, self.t3]
+ self.i3 = Item.objects.create(name='three', created=time3, creator=self.a2, note=self.n3)
+ i4 = Item.objects.create(name='four', created=time4, creator=self.a4, note=self.n3)
+ i4.tags = [t4]
+
+ self.r1 = Report.objects.create(name='r1', creator=self.a1)
+ Report.objects.create(name='r2', creator=a3)
+ Report.objects.create(name='r3')
+
+ # Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering
+ # will be rank3, rank2, rank1.
+ self.rank1 = Ranking.objects.create(rank=2, author=self.a2)
+
+ Cover.objects.create(title="first", item=i4)
+ Cover.objects.create(title="second", item=self.i2)
+
+ def test_ticket1050(self):
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__isnull=True),
+ ['<Item: three>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__id__isnull=True),
+ ['<Item: three>']
+ )
+
+ def test_ticket1801(self):
+ self.assertQuerysetEqual(
+ Author.objects.filter(item=self.i2),
+ ['<Author: a2>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(item=self.i3),
+ ['<Author: a2>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(item=self.i2) & Author.objects.filter(item=self.i3),
+ ['<Author: a2>']
+ )
+
+ def test_ticket2306(self):
+ # Checking that no join types are "left outer" joins.
+ query = Item.objects.filter(tags=self.t2).query
+ self.assertTrue(query.LOUTER not in [x[2] for x in query.alias_map.values()])
+
+ self.assertQuerysetEqual(
+ Item.objects.filter(Q(tags=self.t1)).order_by('name'),
+ ['<Item: one>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(Q(tags=self.t1)).filter(Q(tags=self.t2)),
+ ['<Item: one>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(Q(tags=self.t1)).filter(Q(creator__name='fred')|Q(tags=self.t2)),
+ ['<Item: one>']
+ )
+
+ # Each filter call is processed "at once" against a single table, so this is
+ # different from the previous example as it tries to find tags that are two
+ # things at once (rather than two tags).
+ self.assertQuerysetEqual(
+ Item.objects.filter(Q(tags=self.t1) & Q(tags=self.t2)),
+ []
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(Q(tags=self.t1), Q(creator__name='fred')|Q(tags=self.t2)),
+ []
+ )
+
+ qs = Author.objects.filter(ranking__rank=2, ranking__id=self.rank1.id)
+ self.assertQuerysetEqual(list(qs), ['<Author: a2>'])
+ self.assertEqual(2, qs.query.count_active_tables(), 2)
+ qs = Author.objects.filter(ranking__rank=2).filter(ranking__id=self.rank1.id)
+ self.assertEqual(qs.query.count_active_tables(), 3)
+
+ def test_ticket4464(self):
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags=self.t1).filter(tags=self.t2),
+ ['<Item: one>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__in=[self.t1, self.t2]).distinct().order_by('name'),
+ ['<Item: one>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__in=[self.t1, self.t2]).filter(tags=self.t3),
+ ['<Item: two>']
+ )
+
+ # Make sure .distinct() works with slicing (this was broken in Oracle).
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__in=[self.t1, self.t2]).order_by('name')[:3],
+ ['<Item: one>', '<Item: one>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__in=[self.t1, self.t2]).distinct().order_by('name')[:3],
+ ['<Item: one>', '<Item: two>']
+ )
+
+ def test_tickets_2080_3592(self):
+ self.assertQuerysetEqual(
+ Author.objects.filter(item__name='one') | Author.objects.filter(name='a3'),
+ ['<Author: a1>', '<Author: a3>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(item__name='one') | Q(name='a3')),
+ ['<Author: a1>', '<Author: a3>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(name='a3') | Q(item__name='one')),
+ ['<Author: a1>', '<Author: a3>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(item__name='three') | Q(report__name='r3')),
+ ['<Author: a2>']
+ )
+
+ def test_ticket6074(self):
+ # Merging two empty result sets shouldn't leave a queryset with no constraints
+ # (which would match everything).
+ self.assertQuerysetEqual(Author.objects.filter(Q(id__in=[])), [])
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(id__in=[])|Q(id__in=[])),
+ []
+ )
+
+ def test_tickets_1878_2939(self):
+ self.assertEqual(Item.objects.values('creator').distinct().count(), 3)
+
+ # Create something with a duplicate 'name' so that we can test multi-column
+ # cases (which require some tricky SQL transformations under the covers).
+ xx = Item(name='four', created=self.time1, creator=self.a2, note=self.n1)
+ xx.save()
+ self.assertEqual(
+ Item.objects.exclude(name='two').values('creator', 'name').distinct().count(),
+ 4
+ )
+ self.assertEqual(
+ Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name', 'foo').distinct().count(),
+ 4
+ )
+ self.assertEqual(
+ Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name').distinct().count(),
+ 4
+ )
+ xx.delete()
+
+ def test_ticket7323(self):
+ self.assertEqual(Item.objects.values('creator', 'name').count(), 4)
+
+ def test_ticket2253(self):
+ q1 = Item.objects.order_by('name')
+ q2 = Item.objects.filter(id=self.i1.id)
+ self.assertQuerysetEqual(
+ q1,
+ ['<Item: four>', '<Item: one>', '<Item: three>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(q2, ['<Item: one>'])
+ self.assertQuerysetEqual(
+ (q1 | q2).order_by('name'),
+ ['<Item: four>', '<Item: one>', '<Item: three>', '<Item: two>']
+ )
+ self.assertQuerysetEqual((q1 & q2).order_by('name'), ['<Item: one>'])
+
+ # FIXME: This is difficult to fix and very much an edge case, so punt for now.
+ # This is related to the order_by() tests, below, but the old bug exhibited
+ # itself here (q2 was pulling too many tables into the combined query with the
+ # new ordering, but only because we have evaluated q2 already).
+ #
+ #self.assertEqual(len((q1 & q2).order_by('name').query.tables), 1)
+
+ q1 = Item.objects.filter(tags=self.t1)
+ q2 = Item.objects.filter(note=self.n3, tags=self.t2)
+ q3 = Item.objects.filter(creator=self.a4)
+ self.assertQuerysetEqual(
+ ((q1 & q2) | q3).order_by('name'),
+ ['<Item: four>', '<Item: one>']
+ )
+
+ def test_tickets_4088_4306(self):
+ self.assertQuerysetEqual(
+ Report.objects.filter(creator=1001),
+ ['<Report: r1>']
+ )
+ self.assertQuerysetEqual(
+ Report.objects.filter(creator__num=1001),
+ ['<Report: r1>']
+ )
+ self.assertQuerysetEqual(Report.objects.filter(creator__id=1001), [])
+ self.assertQuerysetEqual(
+ Report.objects.filter(creator__id=self.a1.id),
+ ['<Report: r1>']
+ )
+ self.assertQuerysetEqual(
+ Report.objects.filter(creator__name='a1'),
+ ['<Report: r1>']
+ )
+
+ def test_ticket4510(self):
+ self.assertQuerysetEqual(
+ Author.objects.filter(report__name='r1'),
+ ['<Author: a1>']
+ )
+
+ def test_ticket7378(self):
+ self.assertQuerysetEqual(self.a1.report_set.all(), ['<Report: r1>'])
+
+ def test_tickets_5324_6704(self):
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__name='t4'),
+ ['<Item: four>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.exclude(tags__name='t4').order_by('name').distinct(),
+ ['<Item: one>', '<Item: three>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.exclude(tags__name='t4').order_by('name').distinct().reverse(),
+ ['<Item: two>', '<Item: three>', '<Item: one>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.exclude(item__name='one').distinct().order_by('name'),
+ ['<Author: a2>', '<Author: a3>', '<Author: a4>']
+ )
+
+ # Excluding across a m2m relation when there is more than one related
+ # object associated was problematic.
+ self.assertQuerysetEqual(
+ Item.objects.exclude(tags__name='t1').order_by('name'),
+ ['<Item: four>', '<Item: three>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.exclude(tags__name='t1').exclude(tags__name='t4'),
+ ['<Item: three>']
+ )
+
+ # Excluding from a relation that cannot be NULL should not use outer joins.
+ query = Item.objects.exclude(creator__in=[self.a1, self.a2]).query
+ self.assertTrue(query.LOUTER not in [x[2] for x in query.alias_map.values()])
+
+ # Similarly, when one of the joins cannot possibly, ever, involve NULL
+ # values (Author -> ExtraInfo, in the following), it should never be
+ # promoted to a left outer join. So the following query should only
+ # involve one "left outer" join (Author -> Item is 0-to-many).
+ qs = Author.objects.filter(id=self.a1.id).filter(Q(extra__note=self.n1)|Q(item__note=self.n3))
+ self.assertEqual(
+ len([x[2] for x in qs.query.alias_map.values() if x[2] == query.LOUTER and qs.query.alias_refcount[x[1]]]),
+ 1
+ )
+
+ # The previous changes shouldn't affect nullable foreign key joins.
+ self.assertQuerysetEqual(
+ Tag.objects.filter(parent__isnull=True).order_by('name'),
+ ['<Tag: t1>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(parent__isnull=True).order_by('name'),
+ ['<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(Q(parent__name='t1') | Q(parent__isnull=True)).order_by('name'),
+ ['<Tag: t4>', '<Tag: t5>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(Q(parent__isnull=True) | Q(parent__name='t1')).order_by('name'),
+ ['<Tag: t4>', '<Tag: t5>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(Q(parent__parent__isnull=True)).order_by('name'),
+ ['<Tag: t4>', '<Tag: t5>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.filter(~Q(parent__parent__isnull=True)).order_by('name'),
+ ['<Tag: t4>', '<Tag: t5>']
+ )
+
+ def test_ticket2091(self):
+ t = Tag.objects.get(name='t4')
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__in=[t]),
+ ['<Item: four>']
+ )
+
+ def test_heterogeneous_qs_combination(self):
+ # Combining querysets built on different models should behave in a well-defined
+ # fashion. We raise an error.
+ self.assertRaisesMessage(
+ AssertionError,
+ 'Cannot combine queries on two different base models.',
+ lambda: Author.objects.all() & Tag.objects.all()
+ )
+ self.assertRaisesMessage(
+ AssertionError,
+ 'Cannot combine queries on two different base models.',
+ lambda: Author.objects.all() | Tag.objects.all()
+ )
+
+ def test_ticket3141(self):
+ self.assertEqual(Author.objects.extra(select={'foo': '1'}).count(), 4)
+ self.assertEqual(
+ Author.objects.extra(select={'foo': '%s'}, select_params=(1,)).count(),
+ 4
+ )
+
+ def test_ticket2400(self):
+ self.assertQuerysetEqual(
+ Author.objects.filter(item__isnull=True),
+ ['<Author: a3>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.filter(item__isnull=True),
+ ['<Tag: t5>']
+ )
+
+ def test_ticket2496(self):
+ self.assertQuerysetEqual(
+ Item.objects.extra(tables=['queries_author']).select_related().order_by('name')[:1],
+ ['<Item: four>']
+ )
+
+ def test_tickets_2076_7256(self):
+ # Ordering on related tables should be possible, even if the table is
+ # not otherwise involved.
+ self.assertQuerysetEqual(
+ Item.objects.order_by('note__note', 'name'),
+ ['<Item: two>', '<Item: four>', '<Item: one>', '<Item: three>']
+ )
+
+ # Ordering on a related field should use the remote model's default
+ # ordering as a final step.
+ self.assertQuerysetEqual(
+ Author.objects.order_by('extra', '-name'),
+ ['<Author: a2>', '<Author: a1>', '<Author: a4>', '<Author: a3>']
+ )
+
+ # Using remote model default ordering can span multiple models (in this
+ # case, Cover is ordered by Item's default, which uses Note's default).
+ self.assertQuerysetEqual(
+ Cover.objects.all(),
+ ['<Cover: first>', '<Cover: second>']
+ )
+
+ # If the remote model does not have a default ordering, we order by its 'id'
+ # field.
+ self.assertQuerysetEqual(
+ Item.objects.order_by('creator', 'name'),
+ ['<Item: one>', '<Item: three>', '<Item: two>', '<Item: four>']
+ )
+
+ # Ordering by a many-valued attribute (e.g. a many-to-many or reverse
+ # ForeignKey) is legal, but the results might not make sense. That
+ # isn't Django's problem. Garbage in, garbage out.
+ self.assertQuerysetEqual(
+ Item.objects.filter(tags__isnull=False).order_by('tags', 'id'),
+ ['<Item: one>', '<Item: two>', '<Item: one>', '<Item: two>', '<Item: four>']
+ )
+
+ # If we replace the default ordering, Django adjusts the required
+ # tables automatically. Item normally requires a join with Note to do
+ # the default ordering, but that isn't needed here.
+ qs = Item.objects.order_by('name')
+ self.assertQuerysetEqual(
+ qs,
+ ['<Item: four>', '<Item: one>', '<Item: three>', '<Item: two>']
+ )
+ self.assertEqual(len(qs.query.tables), 1)
+
+ def test_tickets_2874_3002(self):
+ qs = Item.objects.select_related().order_by('note__note', 'name')
+ self.assertQuerysetEqual(
+ qs,
+ ['<Item: two>', '<Item: four>', '<Item: one>', '<Item: three>']
+ )
+
+ # This is also a good select_related() test because there are multiple
+ # Note entries in the SQL. The two Note items should be different.
+ self.assertTrue(repr(qs[0].note), '<Note: n2>')
+ self.assertEqual(repr(qs[0].creator.extra.note), '<Note: n1>')
+
+ def test_ticket3037(self):
+ self.assertQuerysetEqual(
+ Item.objects.filter(Q(creator__name='a3', name='two')|Q(creator__name='a4', name='four')),
+ ['<Item: four>']
+ )
+
+ def test_tickets_5321_7070(self):
+ # Ordering columns must be included in the output columns. Note that
+ # this means results that might otherwise be distinct are not (if there
+ # are multiple values in the ordering cols), as in this example. This
+ # isn't a bug; it's a warning to be careful with the selection of
+ # ordering columns.
+ self.assertValueQuerysetEqual(
+ Note.objects.values('misc').distinct().order_by('note', '-misc'),
+ [{'misc': u'foo'}, {'misc': u'bar'}, {'misc': u'foo'}]
+ )
+
+ def test_ticket4358(self):
+ # If you don't pass any fields to values(), relation fields are
+ # returned as "foo_id" keys, not "foo". For consistency, you should be
+ # able to pass "foo_id" in the fields list and have it work, too. We
+ # actually allow both "foo" and "foo_id".
+
+ # The *_id version is returned by default.
+ self.assertTrue('note_id' in ExtraInfo.objects.values()[0])
+
+ # You can also pass it in explicitly.
+ self.assertValueQuerysetEqual(
+ ExtraInfo.objects.values('note_id'),
+ [{'note_id': 1}, {'note_id': 2}]
+ )
+
+ # ...or use the field name.
+ self.assertValueQuerysetEqual(
+ ExtraInfo.objects.values('note'),
+ [{'note': 1}, {'note': 2}]
+ )
+
+ def test_ticket2902(self):
+ # Parameters can be given to extra_select, *if* you use a SortedDict.
+
+ # (First we need to know which order the keys fall in "naturally" on
+ # your system, so we can put things in the wrong way around from
+ # normal. A normal dict would thus fail.)
+ s = [('a', '%s'), ('b', '%s')]
+ params = ['one', 'two']
+ if {'a': 1, 'b': 2}.keys() == ['a', 'b']:
+ s.reverse()
+ params.reverse()
+
+ # This slightly odd comparison works around the fact that PostgreSQL will
+ # return 'one' and 'two' as strings, not Unicode objects. It's a side-effect of
+ # using constants here and not a real concern.
+ d = Item.objects.extra(select=SortedDict(s), select_params=params).values('a', 'b')[0]
+ self.assertEqual(d, {'a': u'one', 'b': u'two'})
+
+ # Order by the number of tags attached to an item.
+ l = Item.objects.extra(select={'count': 'select count(*) from queries_item_tags where queries_item_tags.item_id = queries_item.id'}).order_by('-count')
+ self.assertEqual([o.count for o in l], [2, 2, 1, 0])
+
+ def test_ticket6154(self):
+ # Multiple filter statements are joined using "AND" all the time.
+
+ self.assertQuerysetEqual(
+ Author.objects.filter(id=self.a1.id).filter(Q(extra__note=self.n1)|Q(item__note=self.n3)),
+ ['<Author: a1>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(extra__note=self.n1)|Q(item__note=self.n3)).filter(id=self.a1.id),
+ ['<Author: a1>']
+ )
+
+ def test_ticket6981(self):
+ self.assertQuerysetEqual(
+ Tag.objects.select_related('parent').order_by('name'),
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>']
+ )
+
+ def test_ticket9926(self):
+ self.assertQuerysetEqual(
+ Tag.objects.select_related("parent", "category").order_by('name'),
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.select_related('parent', "parent__category").order_by('name'),
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>']
+ )
+
+ def test_tickets_6180_6203(self):
+ # Dates with limits and/or counts
+ self.assertEqual(Item.objects.count(), 4)
+ self.assertEqual(Item.objects.dates('created', 'month').count(), 1)
+ self.assertEqual(Item.objects.dates('created', 'day').count(), 2)
+ self.assertEqual(len(Item.objects.dates('created', 'day')), 2)
+ self.assertEqual(Item.objects.dates('created', 'day')[0], datetime.datetime(2007, 12, 19, 0, 0))
+
+ def test_tickets_7087_12242(self):
+ # Dates with extra select columns
+ self.assertQuerysetEqual(
+ Item.objects.dates('created', 'day').extra(select={'a': 1}),
+ ['datetime.datetime(2007, 12, 19, 0, 0)', 'datetime.datetime(2007, 12, 20, 0, 0)']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.extra(select={'a': 1}).dates('created', 'day'),
+ ['datetime.datetime(2007, 12, 19, 0, 0)', 'datetime.datetime(2007, 12, 20, 0, 0)']
+ )
+
+ name="one"
+ self.assertQuerysetEqual(
+ Item.objects.dates('created', 'day').extra(where=['name=%s'], params=[name]),
+ ['datetime.datetime(2007, 12, 19, 0, 0)']
+ )
+
+ self.assertQuerysetEqual(
+ Item.objects.extra(where=['name=%s'], params=[name]).dates('created', 'day'),
+ ['datetime.datetime(2007, 12, 19, 0, 0)']
+ )
+
+ def test_ticket7155(self):
+ # Nullable dates
+ self.assertQuerysetEqual(
+ Item.objects.dates('modified', 'day'),
+ ['datetime.datetime(2007, 12, 19, 0, 0)']
+ )
+
+ def test_ticket7098(self):
+ # Make sure semi-deprecated ordering by related models syntax still
+ # works.
+ self.assertValueQuerysetEqual(
+ Item.objects.values('note__note').order_by('queries_note.note', 'id'),
+ [{'note__note': u'n2'}, {'note__note': u'n3'}, {'note__note': u'n3'}, {'note__note': u'n3'}]
+ )
+
+ def test_ticket7096(self):
+ # Make sure exclude() with multiple conditions continues to work.
+ self.assertQuerysetEqual(
+ Tag.objects.filter(parent=self.t1, name='t3').order_by('name'),
+ ['<Tag: t3>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(parent=self.t1, name='t3').order_by('name'),
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t4>', '<Tag: t5>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.exclude(tags__name='t1', name='one').order_by('name').distinct(),
+ ['<Item: four>', '<Item: three>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(name__in=['three', 'four']).exclude(tags__name='t1').order_by('name'),
+ ['<Item: four>', '<Item: three>']
+ )
+
+ # More twisted cases, involving nested negations.
+ self.assertQuerysetEqual(
+ Item.objects.exclude(~Q(tags__name='t1', name='one')),
+ ['<Item: one>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(~Q(tags__name='t1', name='one'), name='two'),
+ ['<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.exclude(~Q(tags__name='t1', name='one'), name='two'),
+ ['<Item: four>', '<Item: one>', '<Item: three>']
+ )
+
+ def test_tickets_7204_7506(self):
+ # Make sure querysets with related fields can be pickled. If this
+ # doesn't crash, it's a Good Thing.
+ pickle.dumps(Item.objects.all())
+
+ def test_ticket7813(self):
+ # We should also be able to pickle things that use select_related().
+ # The only tricky thing here is to ensure that we do the related
+ # selections properly after unpickling.
+ qs = Item.objects.select_related()
+ query = qs.query.get_compiler(qs.db).as_sql()[0]
+ query2 = pickle.loads(pickle.dumps(qs.query))
+ self.assertEqual(
+ query2.get_compiler(qs.db).as_sql()[0],
+ query
+ )
+
+ def test_deferred_load_qs_pickling(self):
+ # Check pickling of deferred-loading querysets
+ qs = Item.objects.defer('name', 'creator')
+ q2 = pickle.loads(pickle.dumps(qs))
+ self.assertEqual(list(qs), list(q2))
+ q3 = pickle.loads(pickle.dumps(qs, pickle.HIGHEST_PROTOCOL))
+ self.assertEqual(list(qs), list(q3))
+
+ def test_ticket7277(self):
+ self.assertQuerysetEqual(
+ self.n1.annotation_set.filter(Q(tag=self.t5) | Q(tag__children=self.t5) | Q(tag__children__children=self.t5)),
+ ['<Annotation: a1>']
+ )
+
+ def test_tickets_7448_7707(self):
+ # Complex objects should be converted to strings before being used in
+ # lookups.
+ self.assertQuerysetEqual(
+ Item.objects.filter(created__in=[self.time1, self.time2]),
+ ['<Item: one>', '<Item: two>']
+ )
+
+ def test_ticket7235(self):
+ # An EmptyQuerySet should not raise exceptions if it is filtered.
+ q = EmptyQuerySet()
+ self.assertQuerysetEqual(q.all(), [])
+ self.assertQuerysetEqual(q.filter(x=10), [])
+ self.assertQuerysetEqual(q.exclude(y=3), [])
+ self.assertQuerysetEqual(q.complex_filter({'pk': 1}), [])
+ self.assertQuerysetEqual(q.select_related('spam', 'eggs'), [])
+ self.assertQuerysetEqual(q.annotate(Count('eggs')), [])
+ self.assertQuerysetEqual(q.order_by('-pub_date', 'headline'), [])
+ self.assertQuerysetEqual(q.distinct(), [])
+ self.assertQuerysetEqual(
+ q.extra(select={'is_recent': "pub_date > '2006-01-01'"}),
+ []
+ )
+ q.query.low_mark = 1
+ self.assertRaisesMessage(
+ AssertionError,
+ 'Cannot change a query once a slice has been taken',
+ q.extra, select={'is_recent': "pub_date > '2006-01-01'"}
+ )
+ self.assertQuerysetEqual(q.reverse(), [])
+ self.assertQuerysetEqual(q.defer('spam', 'eggs'), [])
+ self.assertQuerysetEqual(q.only('spam', 'eggs'), [])
+
+ def test_ticket7791(self):
+ # There were "issues" when ordering and distinct-ing on fields related
+ # via ForeignKeys.
+ self.assertEqual(
+ len(Note.objects.order_by('extrainfo__info').distinct()),
+ 3
+ )
+
+ # Pickling of DateQuerySets used to fail
+ qs = Item.objects.dates('created', 'month')
+ _ = pickle.loads(pickle.dumps(qs))
+
+ def test_ticket9997(self):
+ # If a ValuesList or Values queryset is passed as an inner query, we
+ # make sure it's only requesting a single value and use that as the
+ # thing to select.
+ self.assertQuerysetEqual(
+ Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values('name')),
+ ['<Tag: t2>', '<Tag: t3>']
+ )
+
+ # Multi-valued values() and values_list() querysets should raise errors.
+ self.assertRaisesMessage(
+ TypeError,
+ 'Cannot use a multi-field ValuesQuerySet as a filter value.',
+ lambda: Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values('name', 'id'))
+ )
+ self.assertRaisesMessage(
+ TypeError,
+ 'Cannot use a multi-field ValuesListQuerySet as a filter value.',
+ lambda: Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values_list('name', 'id'))
+ )
+
+ def test_ticket9985(self):
+ # qs.values_list(...).values(...) combinations should work.
+ self.assertValueQuerysetEqual(
+ Note.objects.values_list("note", flat=True).values("id").order_by("id"),
+ [{'id': 1}, {'id': 2}, {'id': 3}]
+ )
+ self.assertQuerysetEqual(
+ Annotation.objects.filter(notes__in=Note.objects.filter(note="n1").values_list('note').values('id')),
+ ['<Annotation: a1>']
+ )
+
+ def test_ticket10205(self):
+ # When bailing out early because of an empty "__in" filter, we need
+ # to set things up correctly internally so that subqueries can continue properly.
+ self.assertEqual(Tag.objects.filter(name__in=()).update(name="foo"), 0)
+
+ def test_ticket10432(self):
+ # Testing an empty "__in" filter with a generator as the value.
+ def f():
+ return iter([])
+ n_obj = Note.objects.all()[0]
+ def g():
+ for i in [n_obj.pk]:
+ yield i
+ self.assertQuerysetEqual(Note.objects.filter(pk__in=f()), [])
+ self.assertEqual(list(Note.objects.filter(pk__in=g())), [n_obj])
+
+ def test_ticket10742(self):
+ # Queries used in an __in clause don't execute subqueries
+
+ subq = Author.objects.filter(num__lt=3000)
+ qs = Author.objects.filter(pk__in=subq)
+ self.assertQuerysetEqual(qs, ['<Author: a1>', '<Author: a2>'])
+
+ # The subquery result cache should not be populated
+ self.assertTrue(subq._result_cache is None)
+
+ subq = Author.objects.filter(num__lt=3000)
+ qs = Author.objects.exclude(pk__in=subq)
+ self.assertQuerysetEqual(qs, ['<Author: a3>', '<Author: a4>'])
+
+ # The subquery result cache should not be populated
+ self.assertTrue(subq._result_cache is None)
+
+ subq = Author.objects.filter(num__lt=3000)
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(pk__in=subq) & Q(name='a1')),
+ ['<Author: a1>']
+ )
+
+ # The subquery result cache should not be populated
+ self.assertTrue(subq._result_cache is None)
+
+ def test_ticket7076(self):
+ # Excluding shouldn't eliminate NULL entries.
+ self.assertQuerysetEqual(
+ Item.objects.exclude(modified=self.time1).order_by('name'),
+ ['<Item: four>', '<Item: three>', '<Item: two>']
+ )
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(parent__name=self.t1.name),
+ ['<Tag: t1>', '<Tag: t4>', '<Tag: t5>']
+ )
+
+ def test_ticket7181(self):
+ # Ordering by related tables should accomodate nullable fields (this
+ # test is a little tricky, since NULL ordering is database dependent.
+ # Instead, we just count the number of results).
+ self.assertEqual(len(Tag.objects.order_by('parent__name')), 5)
+
+ # Empty querysets can be merged with others.
+ self.assertQuerysetEqual(
+ Note.objects.none() | Note.objects.all(),
+ ['<Note: n1>', '<Note: n2>', '<Note: n3>']
+ )
+ self.assertQuerysetEqual(
+ Note.objects.all() | Note.objects.none(),
+ ['<Note: n1>', '<Note: n2>', '<Note: n3>']
+ )
+ self.assertQuerysetEqual(Note.objects.none() & Note.objects.all(), [])
+ self.assertQuerysetEqual(Note.objects.all() & Note.objects.none(), [])
+
+ def test_ticket9411(self):
+ # Make sure bump_prefix() (an internal Query method) doesn't (re-)break. It's
+ # sufficient that this query runs without error.
+ qs = Tag.objects.values_list('id', flat=True).order_by('id')
+ qs.query.bump_prefix()
+ first = qs[0]
+ self.assertEqual(list(qs), range(first, first+5))
+
+ def test_ticket8439(self):
+ # Complex combinations of conjunctions, disjunctions and nullable
+ # relations.
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(item__note__extrainfo=self.e2)|Q(report=self.r1, name='xyz')),
+ ['<Author: a2>']
+ )
+ self.assertQuerysetEqual(
+ Author.objects.filter(Q(report=self.r1, name='xyz')|Q(item__note__extrainfo=self.e2)),
+ ['<Author: a2>']
+ )
+ self.assertQuerysetEqual(
+ Annotation.objects.filter(Q(tag__parent=self.t1)|Q(notes__note='n1', name='a1')),
+ ['<Annotation: a1>']
+ )
+ xx = ExtraInfo.objects.create(info='xx', note=self.n3)
+ self.assertQuerysetEqual(
+ Note.objects.filter(Q(extrainfo__author=self.a1)|Q(extrainfo=xx)),
+ ['<Note: n1>', '<Note: n3>']
+ )
+ xx.delete()
+ q = Note.objects.filter(Q(extrainfo__author=self.a1)|Q(extrainfo=xx)).query
+ self.assertEqual(
+ len([x[2] for x in q.alias_map.values() if x[2] == q.LOUTER and q.alias_refcount[x[1]]]),
+ 1
+ )
+
+
+class Queries2Tests(TestCase):
+ def setUp(self):
+ Number.objects.create(num=4)
+ Number.objects.create(num=8)
+ Number.objects.create(num=12)
+
+ def test_ticket4289(self):
+ # A slight variation on the restricting the filtering choices by the
+ # lookup constraints.
+ self.assertQuerysetEqual(Number.objects.filter(num__lt=4), [])
+ self.assertQuerysetEqual(Number.objects.filter(num__gt=8, num__lt=12), [])
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__gt=8, num__lt=13),
+ ['<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(Q(num__lt=4) | Q(num__gt=8, num__lt=12)),
+ []
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(Q(num__gt=8, num__lt=12) | Q(num__lt=4)),
+ []
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(Q(num__gt=8) & Q(num__lt=12) | Q(num__lt=4)),
+ []
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(Q(num__gt=7) & Q(num__lt=12) | Q(num__lt=4)),
+ ['<Number: 8>']
+ )
+
+ def test_ticket12239(self):
+ # Float was being rounded to integer on gte queries on integer field. Tests
+ # show that gt, lt, gte, and lte work as desired. Note that the fix changes
+ # get_prep_lookup for gte and lt queries only.
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__gt=11.9),
+ ['<Number: 12>']
+ )
+ self.assertQuerysetEqual(Number.objects.filter(num__gt=12), [])
+ self.assertQuerysetEqual(Number.objects.filter(num__gt=12.0), [])
+ self.assertQuerysetEqual(Number.objects.filter(num__gt=12.1), [])
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lt=12),
+ ['<Number: 4>', '<Number: 8>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lt=12.0),
+ ['<Number: 4>', '<Number: 8>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lt=12.1),
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__gte=11.9),
+ ['<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__gte=12),
+ ['<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__gte=12.0),
+ ['<Number: 12>']
+ )
+ self.assertQuerysetEqual(Number.objects.filter(num__gte=12.1), [])
+ self.assertQuerysetEqual(Number.objects.filter(num__gte=12.9), [])
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lte=11.9),
+ ['<Number: 4>', '<Number: 8>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lte=12),
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lte=12.0),
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lte=12.1),
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(num__lte=12.9),
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ )
+
+ def test_ticket7411(self):
+ # Saving to db must work even with partially read result set in another
+ # cursor.
+ for num in range(2 * ITER_CHUNK_SIZE + 1):
+ _ = Number.objects.create(num=num)
+
+ for i, obj in enumerate(Number.objects.all()):
+ obj.save()
+ if i > 10: break
+
+ def test_ticket7759(self):
+ # Count should work with a partially read result set.
+ count = Number.objects.count()
+ qs = Number.objects.all()
+ def run():
+ for obj in qs:
+ return qs.count() == count
+ self.assertTrue(run())
+
+
+class Queries3Tests(BaseQuerysetTest):
+ def test_ticket7107(self):
+ # This shouldn't create an infinite loop.
+ self.assertQuerysetEqual(Valid.objects.all(), [])
+
+ def test_ticket8683(self):
+ # Raise proper error when a DateQuerySet gets passed a wrong type of
+ # field
+ self.assertRaisesMessage(
+ AssertionError,
+ "'name' isn't a DateField.",
+ Item.objects.dates, 'name', 'month'
+ )
+
+class Queries4Tests(BaseQuerysetTest):
+ def setUp(self):
+ generic = NamedCategory.objects.create(name="Generic")
+ self.t1 = Tag.objects.create(name='t1', category=generic)
+
+ n1 = Note.objects.create(note='n1', misc='foo', id=1)
+ n2 = Note.objects.create(note='n2', misc='bar', id=2)
+
+ e1 = ExtraInfo.objects.create(info='e1', note=n1)
+ e2 = ExtraInfo.objects.create(info='e2', note=n2)
+
+ a1 = Author.objects.create(name='a1', num=1001, extra=e1)
+ a3 = Author.objects.create(name='a3', num=3003, extra=e2)
+
+ Report.objects.create(name='r1', creator=a1)
+ Report.objects.create(name='r2', creator=a3)
+ Report.objects.create(name='r3')
+
+ def test_ticket7095(self):
+ # Updates that are filtered on the model being updated are somewhat
+ # tricky in MySQL. This exercises that case.
+ ManagedModel.objects.create(data='mm1', tag=self.t1, public=True)
+ self.assertEqual(ManagedModel.objects.update(data='mm'), 1)
+
+ # A values() or values_list() query across joined models must use outer
+ # joins appropriately.
+ # Note: In Oracle, we expect a null CharField to return u'' instead of
+ # None.
+ if connection.features.interprets_empty_strings_as_nulls:
+ expected_null_charfield_repr = u''
+ else:
+ expected_null_charfield_repr = None
+ self.assertValueQuerysetEqual(
+ Report.objects.values_list("creator__extra__info", flat=True).order_by("name"),
+ [u'e1', u'e2', expected_null_charfield_repr],
+ )
+
+ # Similarly for select_related(), joins beyond an initial nullable join
+ # must use outer joins so that all results are included.
+ self.assertQuerysetEqual(
+ Report.objects.select_related("creator", "creator__extra").order_by("name"),
+ ['<Report: r1>', '<Report: r2>', '<Report: r3>']
+ )
+
+ # When there are multiple paths to a table from another table, we have
+ # to be careful not to accidentally reuse an inappropriate join when
+ # using select_related(). We used to return the parent's Detail record
+ # here by mistake.
+
+ d1 = Detail.objects.create(data="d1")
+ d2 = Detail.objects.create(data="d2")
+ m1 = Member.objects.create(name="m1", details=d1)
+ m2 = Member.objects.create(name="m2", details=d2)
+ Child.objects.create(person=m2, parent=m1)
+ obj = m1.children.select_related("person__details")[0]
+ self.assertEqual(obj.person.details.data, u'd2')
+
+ def test_order_by_resetting(self):
+ # Calling order_by() with no parameters removes any existing ordering on the
+ # model. But it should still be possible to add new ordering after that.
+ qs = Author.objects.order_by().order_by('name')
+ self.assertTrue('ORDER BY' in qs.query.get_compiler(qs.db).as_sql()[0])
+
+ def test_ticket10181(self):
+ # Avoid raising an EmptyResultSet if an inner query is probably
+ # empty (and hence, not executed).
+ self.assertQuerysetEqual(
+ Tag.objects.filter(id__in=Tag.objects.filter(id__in=[])),
+ []
+ )
+
+
+class Queries5Tests(TestCase):
+ def setUp(self):
+ # Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering
+ # will be rank3, rank2, rank1.
+ n1 = Note.objects.create(note='n1', misc='foo', id=1)
+ n2 = Note.objects.create(note='n2', misc='bar', id=2)
+ e1 = ExtraInfo.objects.create(info='e1', note=n1)
+ e2 = ExtraInfo.objects.create(info='e2', note=n2)
+ a1 = Author.objects.create(name='a1', num=1001, extra=e1)
+ a2 = Author.objects.create(name='a2', num=2002, extra=e1)
+ a3 = Author.objects.create(name='a3', num=3003, extra=e2)
+ self.rank1 = Ranking.objects.create(rank=2, author=a2)
+ Ranking.objects.create(rank=1, author=a3)
+ Ranking.objects.create(rank=3, author=a1)
+
+ def test_ordering(self):
+ # Cross model ordering is possible in Meta, too.
+ self.assertQuerysetEqual(
+ Ranking.objects.all(),
+ ['<Ranking: 3: a1>', '<Ranking: 2: a2>', '<Ranking: 1: a3>']
+ )
+ self.assertQuerysetEqual(
+ Ranking.objects.all().order_by('rank'),
+ ['<Ranking: 1: a3>', '<Ranking: 2: a2>', '<Ranking: 3: a1>']
+ )
+
+
+ # Ordering of extra() pieces is possible, too and you can mix extra
+ # fields and model fields in the ordering.
+ self.assertQuerysetEqual(
+ Ranking.objects.extra(tables=['django_site'], order_by=['-django_site.id', 'rank']),
+ ['<Ranking: 1: a3>', '<Ranking: 2: a2>', '<Ranking: 3: a1>']
+ )
+
+ qs = Ranking.objects.extra(select={'good': 'case when rank > 2 then 1 else 0 end'})
+ self.assertEqual(
+ [o.good for o in qs.extra(order_by=('-good',))],
+ [True, False, False]
+ )
+ self.assertQuerysetEqual(
+ qs.extra(order_by=('-good', 'id')),
+ ['<Ranking: 3: a1>', '<Ranking: 2: a2>', '<Ranking: 1: a3>']
+ )
+
+ # Despite having some extra aliases in the query, we can still omit
+ # them in a values() query.
+ dicts = qs.values('id', 'rank').order_by('id')
+ self.assertEqual(
+ [d.items()[1] for d in dicts],
+ [('rank', 2), ('rank', 1), ('rank', 3)]
+ )
+
+ def test_ticket7256(self):
+ # An empty values() call includes all aliases, including those from an
+ # extra()
+ qs = Ranking.objects.extra(select={'good': 'case when rank > 2 then 1 else 0 end'})
+ dicts = qs.values().order_by('id')
+ for d in dicts: del d['id']; del d['author_id']
+ self.assertEqual(
+ [sorted(d.items()) for d in dicts],
+ [[('good', 0), ('rank', 2)], [('good', 0), ('rank', 1)], [('good', 1), ('rank', 3)]]
+ )
+
+ def test_ticket7045(self):
+ # Extra tables used to crash SQL construction on the second use.
+ qs = Ranking.objects.extra(tables=['django_site'])
+ qs.query.get_compiler(qs.db).as_sql()
+ # test passes if this doesn't raise an exception.
+ qs.query.get_compiler(qs.db).as_sql()
+
+ def test_ticket9848(self):
+ # Make sure that updates which only filter on sub-tables don't
+ # inadvertently update the wrong records (bug #9848).
+
+ # Make sure that the IDs from different tables don't happen to match.
+ self.assertQuerysetEqual(
+ Ranking.objects.filter(author__name='a1'),
+ ['<Ranking: 3: a1>']
+ )
+ self.assertEqual(
+ Ranking.objects.filter(author__name='a1').update(rank='4'),
+ 1
+ )
+ r = Ranking.objects.filter(author__name='a1')[0]
+ self.assertNotEqual(r.id, r.author.id)
+ self.assertEqual(r.rank, 4)
+ r.rank = 3
+ r.save()
+ self.assertQuerysetEqual(
+ Ranking.objects.all(),
+ ['<Ranking: 3: a1>', '<Ranking: 2: a2>', '<Ranking: 1: a3>']
+ )
+
+ def test_ticket5261(self):
+ self.assertQuerysetEqual(
+ Note.objects.exclude(Q()),
+ ['<Note: n1>', '<Note: n2>']
+ )
+
+
+class SelectRelatedTests(TestCase):
+ def test_tickets_3045_3288(self):
+ # Once upon a time, select_related() with circular relations would loop
+ # infinitely if you forgot to specify "depth". Now we set an arbitrary
+ # default upper bound.
+ self.assertQuerysetEqual(X.objects.all(), [])
+ self.assertQuerysetEqual(X.objects.select_related(), [])
+
+
+class SubclassFKTests(TestCase):
+ def test_ticket7778(self):
+ # Model subclasses could not be deleted if a nullable foreign key
+ # relates to a model that relates back.
+
+ num_celebs = Celebrity.objects.count()
+ tvc = TvChef.objects.create(name="Huey")
+ self.assertEqual(Celebrity.objects.count(), num_celebs + 1)
+ Fan.objects.create(fan_of=tvc)
+ Fan.objects.create(fan_of=tvc)
+ tvc.delete()
+
+ # The parent object should have been deleted as well.
+ self.assertEqual(Celebrity.objects.count(), num_celebs)
+
+
+class CustomPkTests(TestCase):
+ def test_ticket7371(self):
+ self.assertQuerysetEqual(Related.objects.order_by('custom'), [])
+
+
+class NullableRelOrderingTests(TestCase):
+ def test_ticket10028(self):
+ # Ordering by model related to nullable relations(!) should use outer
+ # joins, so that all results are included.
+ _ = Plaything.objects.create(name="p1")
+ self.assertQuerysetEqual(
+ Plaything.objects.all(),
+ ['<Plaything: p1>']
+ )
+
+
+class DisjunctiveFilterTests(TestCase):
+ def setUp(self):
+ self.n1 = Note.objects.create(note='n1', misc='foo', id=1)
+ ExtraInfo.objects.create(info='e1', note=self.n1)
+
+ def test_ticket7872(self):
+ # Another variation on the disjunctive filtering theme.
+
+ # For the purposes of this regression test, it's important that there is no
+ # Join object releated to the LeafA we create.
+ LeafA.objects.create(data='first')
+ self.assertQuerysetEqual(LeafA.objects.all(), ['<LeafA: first>'])
+ self.assertQuerysetEqual(
+ LeafA.objects.filter(Q(data='first')|Q(join__b__data='second')),
+ ['<LeafA: first>']
+ )
+
+ def test_ticket8283(self):
+ # Checking that applying filters after a disjunction works correctly.
+ self.assertQuerysetEqual(
+ (ExtraInfo.objects.filter(note=self.n1)|ExtraInfo.objects.filter(info='e2')).filter(note=self.n1),
+ ['<ExtraInfo: e1>']
+ )
+ self.assertQuerysetEqual(
+ (ExtraInfo.objects.filter(info='e2')|ExtraInfo.objects.filter(note=self.n1)).filter(note=self.n1),
+ ['<ExtraInfo: e1>']
+ )
+
+
+class Queries6Tests(TestCase):
+ def setUp(self):
+ generic = NamedCategory.objects.create(name="Generic")
+ t1 = Tag.objects.create(name='t1', category=generic)
+ t2 = Tag.objects.create(name='t2', parent=t1, category=generic)
+ t3 = Tag.objects.create(name='t3', parent=t1)
+ t4 = Tag.objects.create(name='t4', parent=t3)
+ t5 = Tag.objects.create(name='t5', parent=t3)
+ n1 = Note.objects.create(note='n1', misc='foo', id=1)
+ ann1 = Annotation.objects.create(name='a1', tag=t1)
+ ann1.notes.add(n1)
+ ann2 = Annotation.objects.create(name='a2', tag=t4)
+
+ # FIXME!! This next test causes really weird PostgreSQL behaviour, but it's
+ # only apparent much later when the full test suite runs. I don't understand
+ # what's going on here yet.
+ ##def test_slicing_and_cache_interaction(self):
+ ## # We can do slicing beyond what is currently in the result cache,
+ ## # too.
+ ##
+ ## # We need to mess with the implementation internals a bit here to decrease the
+ ## # cache fill size so that we don't read all the results at once.
+ ## from django.db.models import query
+ ## query.ITER_CHUNK_SIZE = 2
+ ## qs = Tag.objects.all()
+ ##
+ ## # Fill the cache with the first chunk.
+ ## self.assertTrue(bool(qs))
+ ## self.assertEqual(len(qs._result_cache), 2)
+ ##
+ ## # Query beyond the end of the cache and check that it is filled out as required.
+ ## self.assertEqual(repr(qs[4]), '<Tag: t5>')
+ ## self.assertEqual(len(qs._result_cache), 5)
+ ##
+ ## # But querying beyond the end of the result set will fail.
+ ## self.assertRaises(IndexError, lambda: qs[100])
+
+ def test_parallel_iterators(self):
+ # Test that parallel iterators work.
+ qs = Tag.objects.all()
+ i1, i2 = iter(qs), iter(qs)
+ self.assertEqual(repr(i1.next()), '<Tag: t1>')
+ self.assertEqual(repr(i1.next()), '<Tag: t2>')
+ self.assertEqual(repr(i2.next()), '<Tag: t1>')
+ self.assertEqual(repr(i2.next()), '<Tag: t2>')
+ self.assertEqual(repr(i2.next()), '<Tag: t3>')
+ self.assertEqual(repr(i1.next()), '<Tag: t3>')
+
+ qs = X.objects.all()
+ self.assertEqual(bool(qs), False)
+ self.assertEqual(bool(qs), False)
+
+ def test_nested_queries_sql(self):
+ # Nested queries should not evaluate the inner query as part of constructing the
+ # SQL (so we should see a nested query here, indicated by two "SELECT" calls).
+ qs = Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy"))
+ self.assertEqual(
+ qs.query.get_compiler(qs.db).as_sql()[0].count('SELECT'),
+ 2
+ )
+
+ def test_tickets_8921_9188(self):
+ # Incorrect SQL was being generated for certain types of exclude()
+ # queries that crossed multi-valued relations (#8921, #9188 and some
+ # pre-emptively discovered cases).
+
+ self.assertQuerysetEqual(
+ PointerA.objects.filter(connection__pointerb__id=1),
+ []
+ )
+ self.assertQuerysetEqual(
+ PointerA.objects.exclude(connection__pointerb__id=1),
+ []
+ )
+
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(children=None),
+ ['<Tag: t1>', '<Tag: t3>']
+ )
+
+ # This example is tricky because the parent could be NULL, so only checking
+ # parents with annotations omits some results (tag t1, in this case).
+ self.assertQuerysetEqual(
+ Tag.objects.exclude(parent__annotation__name="a1"),
+ ['<Tag: t1>', '<Tag: t4>', '<Tag: t5>']
+ )
+
+ # The annotation->tag link is single values and tag->children links is
+ # multi-valued. So we have to split the exclude filter in the middle
+ # and then optimise the inner query without losing results.
+ self.assertQuerysetEqual(
+ Annotation.objects.exclude(tag__children__name="t2"),
+ ['<Annotation: a2>']
+ )
+
+ # Nested queries are possible (although should be used with care, since
+ # they have performance problems on backends like MySQL.
+
+ self.assertQuerysetEqual(
+ Annotation.objects.filter(notes__in=Note.objects.filter(note="n1")),
+ ['<Annotation: a1>']
+ )
+
+ def test_ticket3739(self):
+ # The all() method on querysets returns a copy of the queryset.
+ q1 = Tag.objects.order_by('name')
+ self.assertTrue(q1 is not q1.all())
+
+
+class GeneratorExpressionTests(TestCase):
+ def test_ticket10432(self):
+ # Using an empty generator expression as the rvalue for an "__in"
+ # lookup is legal.
+ self.assertQuerysetEqual(
+ Note.objects.filter(pk__in=(x for x in ())),
+ []
+ )
+
+
+class ComparisonTests(TestCase):
+ def setUp(self):
+ self.n1 = Note.objects.create(note='n1', misc='foo', id=1)
+ e1 = ExtraInfo.objects.create(info='e1', note=self.n1)
+ self.a2 = Author.objects.create(name='a2', num=2002, extra=e1)
+
+ def test_ticket8597(self):
+ # Regression tests for case-insensitive comparisons
+ _ = Item.objects.create(name="a_b", created=datetime.datetime.now(), creator=self.a2, note=self.n1)
+ _ = Item.objects.create(name="x%y", created=datetime.datetime.now(), creator=self.a2, note=self.n1)
+ self.assertQuerysetEqual(
+ Item.objects.filter(name__iexact="A_b"),
+ ['<Item: a_b>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(name__iexact="x%Y"),
+ ['<Item: x%y>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(name__istartswith="A_b"),
+ ['<Item: a_b>']
+ )
+ self.assertQuerysetEqual(
+ Item.objects.filter(name__iendswith="A_b"),
+ ['<Item: a_b>']
+ )
+
+
+class ExistsSql(TestCase):
+ def setUp(self):
+ settings.DEBUG = True
+
+ def test_exists(self):
+ self.assertFalse(Tag.objects.exists())
+ # Ok - so the exist query worked - but did it include too many columns?
+ self.assertTrue("id" not in connection.queries[-1]['sql'] and "name" not in connection.queries[-1]['sql'])
+
+ def tearDown(self):
+ settings.DEBUG = False
+
+
+class QuerysetOrderedTests(unittest.TestCase):
+ """
+ Tests for the Queryset.ordered attribute.
+ """
+
+ def test_no_default_or_explicit_ordering(self):
+ self.assertEqual(Annotation.objects.all().ordered, False)
+
+ def test_cleared_default_ordering(self):
+ self.assertEqual(Tag.objects.all().ordered, True)
+ self.assertEqual(Tag.objects.all().order_by().ordered, False)
+
+ def test_explicit_ordering(self):
+ self.assertEqual(Annotation.objects.all().order_by('id').ordered, True)
+
+ def test_order_by_extra(self):
+ self.assertEqual(Annotation.objects.all().extra(order_by=['id']).ordered, True)
+
+ def test_annotated_ordering(self):
+ qs = Annotation.objects.annotate(num_notes=Count('notes'))
+ self.assertEqual(qs.ordered, False)
+ self.assertEqual(qs.order_by('num_notes').ordered, True)
+
+
+class SubqueryTests(TestCase):
+ def setUp(self):
+ DumbCategory.objects.create(id=1)
+ DumbCategory.objects.create(id=2)
+ DumbCategory.objects.create(id=3)
+
+ def test_ordered_subselect(self):
+ "Subselects honor any manual ordering"
+ try:
+ query = DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[0:2])
+ self.assertEquals(set(query.values_list('id', flat=True)), set([2,3]))
+
+ query = DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[:2])
+ self.assertEquals(set(query.values_list('id', flat=True)), set([2,3]))
+
+ query = DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[2:])
+ self.assertEquals(set(query.values_list('id', flat=True)), set([1]))
+ except DatabaseError:
+ # Oracle and MySQL both have problems with sliced subselects.
+ # This prevents us from even evaluating this test case at all.
+ # Refs #10099
+ self.assertFalse(connections[DEFAULT_DB_ALIAS].features.allow_sliced_subqueries)
+
+ def test_sliced_delete(self):
+ "Delete queries can safely contain sliced subqueries"
+ try:
+ DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[0:1]).delete()
+ self.assertEquals(set(DumbCategory.objects.values_list('id', flat=True)), set([1,2]))
+ except DatabaseError:
+ # Oracle and MySQL both have problems with sliced subselects.
+ # This prevents us from even evaluating this test case at all.
+ # Refs #10099
+ self.assertFalse(connections[DEFAULT_DB_ALIAS].features.allow_sliced_subqueries)
+
+
+class CloneTests(TestCase):
+ def test_evaluated_queryset_as_argument(self):
+ "#13227 -- If a queryset is already evaluated, it can still be used as a query arg"
+ n = Note(note='Test1', misc='misc')
+ n.save()
+ e = ExtraInfo(info='good', note=n)
+ e.save()
+
+ n_list = Note.objects.all()
+ # Evaluate the Note queryset, populating the query cache
+ list(n_list)
+ # Use the note queryset in a query, and evalute
+ # that query in a way that involves cloning.
+ try:
+ self.assertEquals(ExtraInfo.objects.filter(note__in=n_list)[0].info, 'good')
+ except:
+ self.fail('Query should be clonable')
+
+
+class EmptyQuerySetTests(TestCase):
+ def test_emptyqueryset_values(self):
+ # #14366 -- Calling .values() on an EmptyQuerySet and then cloning that
+ # should not cause an error"
+ self.assertQuerysetEqual(
+ Number.objects.none().values('num').order_by('num'), []
+ )
+
+ def test_values_subquery(self):
+ self.assertQuerysetEqual(
+ Number.objects.filter(pk__in=Number.objects.none().values("pk")),
+ []
+ )
+ self.assertQuerysetEqual(
+ Number.objects.filter(pk__in=Number.objects.none().values_list("pk")),
+ []
+ )
+
+
+class ValuesQuerysetTests(BaseQuerysetTest):
+ def test_flat_values_lits(self):
+ Number.objects.create(num=72)
+ qs = Number.objects.values_list("num")
+ qs = qs.values_list("num", flat=True)
+ self.assertValueQuerysetEqual(
+ qs, [72]
+ )
+
+
+class WeirdQuerysetSlicingTests(BaseQuerysetTest):
+ def setUp(self):
+ Number.objects.create(num=1)
+ Number.objects.create(num=2)
+
+ Article.objects.create(name='one', created=datetime.datetime.now())
+ Article.objects.create(name='two', created=datetime.datetime.now())
+ Article.objects.create(name='three', created=datetime.datetime.now())
+ Article.objects.create(name='four', created=datetime.datetime.now())
+
+ def test_tickets_7698_10202(self):
+ # People like to slice with '0' as the high-water mark.
+ self.assertQuerysetEqual(Article.objects.all()[0:0], [])
+ self.assertQuerysetEqual(Article.objects.all()[0:0][:10], [])
+ self.assertEqual(Article.objects.all()[:0].count(), 0)
+ self.assertRaisesMessage(
+ AssertionError,
+ 'Cannot change a query once a slice has been taken.',
+ Article.objects.all()[:0].latest, 'created'
+ )
+
+
+class EscapingTests(TestCase):
+ def test_ticket_7302(self):
+ # Reserved names are appropriately escaped
+ _ = ReservedName.objects.create(name='a', order=42)
+ ReservedName.objects.create(name='b', order=37)
+ self.assertQuerysetEqual(
+ ReservedName.objects.all().order_by('order'),
+ ['<ReservedName: b>', '<ReservedName: a>']
+ )
+ self.assertQuerysetEqual(
+ ReservedName.objects.extra(select={'stuff':'name'}, order_by=('order','stuff')),
+ ['<ReservedName: b>', '<ReservedName: a>']
+ )
+
+
+# In Python 2.6 beta releases, exceptions raised in __len__ are swallowed
+# (Python issue 1242657), so these cases return an empty list, rather than
+# raising an exception. Not a lot we can do about that, unfortunately, due to
+# the way Python handles list() calls internally. Thus, we skip the tests for
+# Python 2.6.
+if sys.version_info[:2] != (2, 6):
+ class OrderingLoopTests(BaseQuerysetTest):
+ def setUp(self):
+ generic = NamedCategory.objects.create(name="Generic")
+ t1 = Tag.objects.create(name='t1', category=generic)
+ t2 = Tag.objects.create(name='t2', parent=t1, category=generic)
+ t3 = Tag.objects.create(name='t3', parent=t1)
+ t4 = Tag.objects.create(name='t4', parent=t3)
+ t5 = Tag.objects.create(name='t5', parent=t3)
+
+ def test_infinite_loop(self):
+ # If you're not careful, it's possible to introduce infinite loops via
+ # default ordering on foreign keys in a cycle. We detect that.
+ self.assertRaisesMessage(
+ FieldError,
+ 'Infinite loop caused by ordering.',
+ lambda: list(LoopX.objects.all()) # Force queryset evaluation with list()
+ )
+ self.assertRaisesMessage(
+ FieldError,
+ 'Infinite loop caused by ordering.',
+ lambda: list(LoopZ.objects.all()) # Force queryset evaluation with list()
+ )
+
+ # Note that this doesn't cause an infinite loop, since the default
+ # ordering on the Tag model is empty (and thus defaults to using "id"
+ # for the related field).
+ self.assertEqual(len(Tag.objects.order_by('parent')), 5)
+
+ # ... but you can still order in a non-recursive fashion amongst linked
+ # fields (the previous test failed because the default ordering was
+ # recursive).
+ self.assertQuerysetEqual(
+ LoopX.objects.all().order_by('y__x__y__x__id'),
+ []
+ )
+
+
+# When grouping without specifying ordering, we add an explicit "ORDER BY NULL"
+# portion in MySQL to prevent unnecessary sorting.
+if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.mysql":
+ class GroupingTests(TestCase):
+ def test_null_ordering_added(self):
+ query = Tag.objects.values_list('parent_id', flat=True).order_by().query
+ query.group_by = ['parent_id']
+ sql = query.get_compiler(DEFAULT_DB_ALIAS).as_sql()[0]
+ fragment = "ORDER BY "
+ pos = sql.find(fragment)
+ self.assertEqual(sql.find(fragment, pos + 1), -1)
+ self.assertEqual(sql.find("NULL", pos + len(fragment)), pos + len(fragment))
+
+
+# Sqlite 3 does not support passing in more than 1000 parameters except by
+# changing a parameter at compilation time.
+if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != "django.db.backends.sqlite3":
+ class InLookupTests(TestCase):
+ def test_ticket14244(self):
+ # Test that the "in" lookup works with lists of 1000 items or more.
+ Number.objects.all().delete()
+ numbers = range(2500)
+ for num in numbers:
+ _ = Number.objects.create(num=num)
+ self.assertEqual(
+ Number.objects.filter(num__in=numbers[:1000]).count(),
+ 1000
+ )
+ self.assertEqual(
+ Number.objects.filter(num__in=numbers[:1001]).count(),
+ 1001
+ )
+ self.assertEqual(
+ Number.objects.filter(num__in=numbers[:2000]).count(),
+ 2000
+ )
+ self.assertEqual(
+ Number.objects.filter(num__in=numbers).count(),
+ 2500
+ )
diff --git a/parts/django/tests/regressiontests/queryset_pickle/__init__.py b/parts/django/tests/regressiontests/queryset_pickle/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/queryset_pickle/__init__.py
diff --git a/parts/django/tests/regressiontests/queryset_pickle/models.py b/parts/django/tests/regressiontests/queryset_pickle/models.py
new file mode 100644
index 0000000..df0a6e6
--- /dev/null
+++ b/parts/django/tests/regressiontests/queryset_pickle/models.py
@@ -0,0 +1,34 @@
+import datetime
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+def standalone_number(self):
+ return 1
+
+class Numbers(object):
+ @staticmethod
+ def get_static_number(self):
+ return 2
+
+ @classmethod
+ def get_class_number(self):
+ return 3
+
+ def get_member_number(self):
+ return 4
+
+nn = Numbers()
+
+class Group(models.Model):
+ name = models.CharField(_('name'), max_length=100)
+
+class Event(models.Model):
+ group = models.ForeignKey(Group)
+
+class Happening(models.Model):
+ when = models.DateTimeField(blank=True, default=datetime.datetime.now)
+ name = models.CharField(blank=True, max_length=100, default=lambda:"test")
+ number1 = models.IntegerField(blank=True, default=standalone_number)
+ number2 = models.IntegerField(blank=True, default=Numbers.get_static_number)
+ number3 = models.IntegerField(blank=True, default=Numbers.get_class_number)
+ number4 = models.IntegerField(blank=True, default=nn.get_member_number)
diff --git a/parts/django/tests/regressiontests/queryset_pickle/tests.py b/parts/django/tests/regressiontests/queryset_pickle/tests.py
new file mode 100644
index 0000000..5c64687
--- /dev/null
+++ b/parts/django/tests/regressiontests/queryset_pickle/tests.py
@@ -0,0 +1,36 @@
+import pickle
+import datetime
+
+from django.test import TestCase
+
+from models import Group, Event, Happening
+
+
+class PickleabilityTestCase(TestCase):
+ def assert_pickles(self, qs):
+ self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs))
+
+ def test_related_field(self):
+ g = Group.objects.create(name="Ponies Who Own Maybachs")
+ self.assert_pickles(Event.objects.filter(group=g.id))
+
+ def test_datetime_callable_default_all(self):
+ self.assert_pickles(Happening.objects.all())
+
+ def test_datetime_callable_default_filter(self):
+ self.assert_pickles(Happening.objects.filter(when=datetime.datetime.now()))
+
+ def test_lambda_as_default(self):
+ self.assert_pickles(Happening.objects.filter(name="test"))
+
+ def test_standalone_method_as_default(self):
+ self.assert_pickles(Happening.objects.filter(number1=1))
+
+ def test_staticmethod_as_default(self):
+ self.assert_pickles(Happening.objects.filter(number2=1))
+
+ def test_classmethod_as_default(self):
+ self.assert_pickles(Happening.objects.filter(number3=1))
+
+ def test_membermethod_as_default(self):
+ self.assert_pickles(Happening.objects.filter(number4=1))
diff --git a/parts/django/tests/regressiontests/requests/__init__.py b/parts/django/tests/regressiontests/requests/__init__.py
new file mode 100644
index 0000000..3a32885
--- /dev/null
+++ b/parts/django/tests/regressiontests/requests/__init__.py
@@ -0,0 +1,3 @@
+"""
+Tests for Django's various Request objects.
+"""
diff --git a/parts/django/tests/regressiontests/requests/models.py b/parts/django/tests/regressiontests/requests/models.py
new file mode 100644
index 0000000..19f81d6
--- /dev/null
+++ b/parts/django/tests/regressiontests/requests/models.py
@@ -0,0 +1 @@
+# Need a models module for the test runner.
diff --git a/parts/django/tests/regressiontests/requests/tests.py b/parts/django/tests/regressiontests/requests/tests.py
new file mode 100644
index 0000000..556d61e
--- /dev/null
+++ b/parts/django/tests/regressiontests/requests/tests.py
@@ -0,0 +1,59 @@
+from datetime import datetime, timedelta
+import time
+import unittest
+
+from django.http import HttpRequest, HttpResponse, parse_cookie
+from django.core.handlers.wsgi import WSGIRequest
+from django.core.handlers.modpython import ModPythonRequest
+from django.utils.http import cookie_date
+
+class RequestsTests(unittest.TestCase):
+
+ def test_httprequest(self):
+ request = HttpRequest()
+ self.assertEqual(request.GET.keys(), [])
+ self.assertEqual(request.POST.keys(), [])
+ self.assertEqual(request.COOKIES.keys(), [])
+ self.assertEqual(request.META.keys(), [])
+
+ def test_wsgirequest(self):
+ request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'})
+ self.assertEqual(request.GET.keys(), [])
+ self.assertEqual(request.POST.keys(), [])
+ self.assertEqual(request.COOKIES.keys(), [])
+ self.assertEqual(set(request.META.keys()), set(['PATH_INFO', 'REQUEST_METHOD', 'SCRIPT_NAME']))
+ self.assertEqual(request.META['PATH_INFO'], 'bogus')
+ self.assertEqual(request.META['REQUEST_METHOD'], 'bogus')
+ self.assertEqual(request.META['SCRIPT_NAME'], '')
+
+ def test_modpythonrequest(self):
+ class FakeModPythonRequest(ModPythonRequest):
+ def __init__(self, *args, **kwargs):
+ super(FakeModPythonRequest, self).__init__(*args, **kwargs)
+ self._get = self._post = self._meta = self._cookies = {}
+
+ class Dummy:
+ def get_options(self):
+ return {}
+
+ req = Dummy()
+ req.uri = 'bogus'
+ request = FakeModPythonRequest(req)
+ self.assertEqual(request.path, 'bogus')
+ self.assertEqual(request.GET.keys(), [])
+ self.assertEqual(request.POST.keys(), [])
+ self.assertEqual(request.COOKIES.keys(), [])
+ self.assertEqual(request.META.keys(), [])
+
+ def test_parse_cookie(self):
+ self.assertEqual(parse_cookie('invalid:key=true'), {})
+
+ def test_httprequest_location(self):
+ request = HttpRequest()
+ self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"),
+ 'https://www.example.com/asdf')
+
+ request.get_host = lambda: 'www.example.com'
+ request.path = ''
+ self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
+ 'http://www.example.com/path/with:colons')
diff --git a/parts/django/tests/regressiontests/reverse_single_related/__init__.py b/parts/django/tests/regressiontests/reverse_single_related/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/reverse_single_related/__init__.py
diff --git a/parts/django/tests/regressiontests/reverse_single_related/models.py b/parts/django/tests/regressiontests/reverse_single_related/models.py
new file mode 100644
index 0000000..a2d8fb0
--- /dev/null
+++ b/parts/django/tests/regressiontests/reverse_single_related/models.py
@@ -0,0 +1,12 @@
+from django.db import models
+
+class SourceManager(models.Manager):
+ def get_query_set(self):
+ return super(SourceManager, self).get_query_set().filter(is_public=True)
+
+class Source(models.Model):
+ is_public = models.BooleanField()
+ objects = SourceManager()
+
+class Item(models.Model):
+ source = models.ForeignKey(Source)
diff --git a/parts/django/tests/regressiontests/reverse_single_related/tests.py b/parts/django/tests/regressiontests/reverse_single_related/tests.py
new file mode 100644
index 0000000..14f3a66
--- /dev/null
+++ b/parts/django/tests/regressiontests/reverse_single_related/tests.py
@@ -0,0 +1,36 @@
+from django.test import TestCase
+
+from regressiontests.reverse_single_related.models import *
+
+class ReverseSingleRelatedTests(TestCase):
+ """
+ Regression tests for an object that cannot access a single related
+ object due to a restrictive default manager.
+ """
+
+ def test_reverse_single_related(self):
+
+ public_source = Source.objects.create(is_public=True)
+ public_item = Item.objects.create(source=public_source)
+
+ private_source = Source.objects.create(is_public=False)
+ private_item = Item.objects.create(source=private_source)
+
+ # Only one source is available via all() due to the custom default manager.
+ self.assertQuerysetEqual(
+ Source.objects.all(),
+ ["<Source: Source object>"]
+ )
+
+ self.assertEquals(public_item.source, public_source)
+
+ # Make sure that an item can still access its related source even if the default
+ # manager doesn't normally allow it.
+ self.assertEquals(private_item.source, private_source)
+
+ # If the manager is marked "use_for_related_fields", it'll get used instead
+ # of the "bare" queryset. Usually you'd define this as a property on the class,
+ # but this approximates that in a way that's easier in tests.
+ Source.objects.use_for_related_fields = True
+ private_item = Item.objects.get(pk=private_item.pk)
+ self.assertRaises(Source.DoesNotExist, lambda: private_item.source)
diff --git a/parts/django/tests/regressiontests/select_related_onetoone/__init__.py b/parts/django/tests/regressiontests/select_related_onetoone/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/select_related_onetoone/__init__.py
diff --git a/parts/django/tests/regressiontests/select_related_onetoone/models.py b/parts/django/tests/regressiontests/select_related_onetoone/models.py
new file mode 100644
index 0000000..3d6da9b
--- /dev/null
+++ b/parts/django/tests/regressiontests/select_related_onetoone/models.py
@@ -0,0 +1,54 @@
+from django.db import models
+
+
+class User(models.Model):
+ username = models.CharField(max_length=100)
+ email = models.EmailField()
+
+ def __unicode__(self):
+ return self.username
+
+
+class UserProfile(models.Model):
+ user = models.OneToOneField(User)
+ city = models.CharField(max_length=100)
+ state = models.CharField(max_length=2)
+
+ def __unicode__(self):
+ return "%s, %s" % (self.city, self.state)
+
+
+class UserStatResult(models.Model):
+ results = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return 'UserStatResults, results = %s' % (self.results,)
+
+
+class UserStat(models.Model):
+ user = models.OneToOneField(User, primary_key=True)
+ posts = models.IntegerField()
+ results = models.ForeignKey(UserStatResult)
+
+ def __unicode__(self):
+ return 'UserStat, posts = %s' % (self.posts,)
+
+
+class StatDetails(models.Model):
+ base_stats = models.OneToOneField(UserStat)
+ comments = models.IntegerField()
+
+ def __unicode__(self):
+ return 'StatDetails, comments = %s' % (self.comments,)
+
+
+class AdvancedUserStat(UserStat):
+ karma = models.IntegerField()
+
+class Image(models.Model):
+ name = models.CharField(max_length=100)
+
+
+class Product(models.Model):
+ name = models.CharField(max_length=100)
+ image = models.OneToOneField(Image, null=True)
diff --git a/parts/django/tests/regressiontests/select_related_onetoone/tests.py b/parts/django/tests/regressiontests/select_related_onetoone/tests.py
new file mode 100644
index 0000000..4ccb584
--- /dev/null
+++ b/parts/django/tests/regressiontests/select_related_onetoone/tests.py
@@ -0,0 +1,94 @@
+from django import db
+from django.conf import settings
+from django.test import TestCase
+
+from models import (User, UserProfile, UserStat, UserStatResult, StatDetails,
+ AdvancedUserStat, Image, Product)
+
+class ReverseSelectRelatedTestCase(TestCase):
+ def setUp(self):
+ # Explicitly enable debug for these tests - we need to count
+ # the queries that have been issued.
+ self.old_debug = settings.DEBUG
+ settings.DEBUG = True
+
+ user = User.objects.create(username="test")
+ userprofile = UserProfile.objects.create(user=user, state="KS",
+ city="Lawrence")
+ results = UserStatResult.objects.create(results='first results')
+ userstat = UserStat.objects.create(user=user, posts=150,
+ results=results)
+ details = StatDetails.objects.create(base_stats=userstat, comments=259)
+
+ user2 = User.objects.create(username="bob")
+ results2 = UserStatResult.objects.create(results='moar results')
+ advstat = AdvancedUserStat.objects.create(user=user2, posts=200, karma=5,
+ results=results2)
+ StatDetails.objects.create(base_stats=advstat, comments=250)
+
+ db.reset_queries()
+
+ def assertQueries(self, queries):
+ self.assertEqual(len(db.connection.queries), queries)
+
+ def tearDown(self):
+ settings.DEBUG = self.old_debug
+
+ def test_basic(self):
+ u = User.objects.select_related("userprofile").get(username="test")
+ self.assertEqual(u.userprofile.state, "KS")
+ self.assertQueries(1)
+
+ def test_follow_next_level(self):
+ u = User.objects.select_related("userstat__results").get(username="test")
+ self.assertEqual(u.userstat.posts, 150)
+ self.assertEqual(u.userstat.results.results, 'first results')
+ self.assertQueries(1)
+
+ def test_follow_two(self):
+ u = User.objects.select_related("userprofile", "userstat").get(username="test")
+ self.assertEqual(u.userprofile.state, "KS")
+ self.assertEqual(u.userstat.posts, 150)
+ self.assertQueries(1)
+
+ def test_follow_two_next_level(self):
+ u = User.objects.select_related("userstat__results", "userstat__statdetails").get(username="test")
+ self.assertEqual(u.userstat.results.results, 'first results')
+ self.assertEqual(u.userstat.statdetails.comments, 259)
+ self.assertQueries(1)
+
+ def test_forward_and_back(self):
+ stat = UserStat.objects.select_related("user__userprofile").get(user__username="test")
+ self.assertEqual(stat.user.userprofile.state, 'KS')
+ self.assertEqual(stat.user.userstat.posts, 150)
+ self.assertQueries(1)
+
+ def test_back_and_forward(self):
+ u = User.objects.select_related("userstat").get(username="test")
+ self.assertEqual(u.userstat.user.username, 'test')
+ self.assertQueries(1)
+
+ def test_not_followed_by_default(self):
+ u = User.objects.select_related().get(username="test")
+ self.assertEqual(u.userstat.posts, 150)
+ self.assertQueries(2)
+
+ def test_follow_from_child_class(self):
+ stat = AdvancedUserStat.objects.select_related('user', 'statdetails').get(posts=200)
+ self.assertEqual(stat.statdetails.comments, 250)
+ self.assertEqual(stat.user.username, 'bob')
+ self.assertQueries(1)
+
+ def test_follow_inheritance(self):
+ stat = UserStat.objects.select_related('user', 'advanceduserstat').get(posts=200)
+ self.assertEqual(stat.advanceduserstat.posts, 200)
+ self.assertEqual(stat.user.username, 'bob')
+ self.assertEqual(stat.advanceduserstat.user.username, 'bob')
+ self.assertQueries(1)
+
+ def test_nullable_relation(self):
+ im = Image.objects.create(name="imag1")
+ p1 = Product.objects.create(name="Django Plushie", image=im)
+ p2 = Product.objects.create(name="Talking Django Plushie")
+
+ self.assertEqual(len(Product.objects.select_related("image")), 2)
diff --git a/parts/django/tests/regressiontests/select_related_regress/__init__.py b/parts/django/tests/regressiontests/select_related_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/select_related_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/select_related_regress/models.py b/parts/django/tests/regressiontests/select_related_regress/models.py
new file mode 100644
index 0000000..3efb19e
--- /dev/null
+++ b/parts/django/tests/regressiontests/select_related_regress/models.py
@@ -0,0 +1,86 @@
+from django.db import models
+
+class Building(models.Model):
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u"Building: %s" % self.name
+
+class Device(models.Model):
+ building = models.ForeignKey('Building')
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u"device '%s' in building %s" % (self.name, self.building)
+
+class Port(models.Model):
+ device = models.ForeignKey('Device')
+ port_number = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u"%s/%s" % (self.device.name, self.port_number)
+
+class Connection(models.Model):
+ start = models.ForeignKey(Port, related_name='connection_start',
+ unique=True)
+ end = models.ForeignKey(Port, related_name='connection_end', unique=True)
+
+ def __unicode__(self):
+ return u"%s to %s" % (self.start, self.end)
+
+# Another non-tree hierarchy that exercises code paths similar to the above
+# example, but in a slightly different configuration.
+class TUser(models.Model):
+ name = models.CharField(max_length=200)
+
+class Person(models.Model):
+ user = models.ForeignKey(TUser, unique=True)
+
+class Organizer(models.Model):
+ person = models.ForeignKey(Person)
+
+class Student(models.Model):
+ person = models.ForeignKey(Person)
+
+class Class(models.Model):
+ org = models.ForeignKey(Organizer)
+
+class Enrollment(models.Model):
+ std = models.ForeignKey(Student)
+ cls = models.ForeignKey(Class)
+
+# Models for testing bug #8036.
+class Country(models.Model):
+ name = models.CharField(max_length=50)
+
+class State(models.Model):
+ name = models.CharField(max_length=50)
+ country = models.ForeignKey(Country)
+
+class ClientStatus(models.Model):
+ name = models.CharField(max_length=50)
+
+class Client(models.Model):
+ name = models.CharField(max_length=50)
+ state = models.ForeignKey(State, null=True)
+ status = models.ForeignKey(ClientStatus)
+
+class SpecialClient(Client):
+ value = models.IntegerField()
+
+# Some model inheritance exercises
+class Parent(models.Model):
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return self.name
+
+class Child(Parent):
+ value = models.IntegerField()
+
+class Item(models.Model):
+ name = models.CharField(max_length=10)
+ child = models.ForeignKey(Child, null=True)
+
+ def __unicode__(self):
+ return self.name
diff --git a/parts/django/tests/regressiontests/select_related_regress/tests.py b/parts/django/tests/regressiontests/select_related_regress/tests.py
new file mode 100644
index 0000000..bfa1e21
--- /dev/null
+++ b/parts/django/tests/regressiontests/select_related_regress/tests.py
@@ -0,0 +1,134 @@
+from django.test import TestCase
+from regressiontests.select_related_regress.models import *
+
+class SelectRelatedRegressTests(TestCase):
+
+ def test_regression_7110(self):
+ """
+ Regression test for bug #7110.
+
+ When using select_related(), we must query the
+ Device and Building tables using two different aliases (each) in order to
+ differentiate the start and end Connection fields. The net result is that
+ both the "connections = ..." queries here should give the same results
+ without pulling in more than the absolute minimum number of tables
+ (history has shown that it's easy to make a mistake in the implementation
+ and include some unnecessary bonus joins).
+ """
+
+ b=Building.objects.create(name='101')
+ dev1=Device.objects.create(name="router", building=b)
+ dev2=Device.objects.create(name="switch", building=b)
+ dev3=Device.objects.create(name="server", building=b)
+ port1=Port.objects.create(port_number='4',device=dev1)
+ port2=Port.objects.create(port_number='7',device=dev2)
+ port3=Port.objects.create(port_number='1',device=dev3)
+ c1=Connection.objects.create(start=port1, end=port2)
+ c2=Connection.objects.create(start=port2, end=port3)
+
+ connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id')
+ self.assertEquals([(c.id, unicode(c.start), unicode(c.end)) for c in connections],
+ [(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')])
+
+ connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id')
+ self.assertEquals([(c.id, unicode(c.start), unicode(c.end)) for c in connections],
+ [(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')])
+
+ # This final query should only join seven tables (port, device and building
+ # twice each, plus connection once).
+ self.assertEquals(connections.query.count_active_tables(), 7)
+
+
+ def test_regression_8106(self):
+ """
+ Regression test for bug #8106.
+
+ Same sort of problem as the previous test, but this time there are
+ more extra tables to pull in as part of the select_related() and some
+ of them could potentially clash (so need to be kept separate).
+ """
+
+ us = TUser.objects.create(name="std")
+ usp = Person.objects.create(user=us)
+ uo = TUser.objects.create(name="org")
+ uop = Person.objects.create(user=uo)
+ s = Student.objects.create(person = usp)
+ o = Organizer.objects.create(person = uop)
+ c = Class.objects.create(org=o)
+ e = Enrollment.objects.create(std=s, cls=c)
+
+ e_related = Enrollment.objects.all().select_related()[0]
+ self.assertEquals(e_related.std.person.user.name, u"std")
+ self.assertEquals(e_related.cls.org.person.user.name, u"org")
+
+ def test_regression_8036(self):
+ """
+ Regression test for bug #8036
+
+ the first related model in the tests below
+ ("state") is empty and we try to select the more remotely related
+ state__country. The regression here was not skipping the empty column results
+ for country before getting status.
+ """
+
+ australia = Country.objects.create(name='Australia')
+ active = ClientStatus.objects.create(name='active')
+ client = Client.objects.create(name='client', status=active)
+
+ self.assertEquals(client.status, active)
+ self.assertEquals(Client.objects.select_related()[0].status, active)
+ self.assertEquals(Client.objects.select_related('state')[0].status, active)
+ self.assertEquals(Client.objects.select_related('state', 'status')[0].status, active)
+ self.assertEquals(Client.objects.select_related('state__country')[0].status, active)
+ self.assertEquals(Client.objects.select_related('state__country', 'status')[0].status, active)
+ self.assertEquals(Client.objects.select_related('status')[0].status, active)
+
+ def test_multi_table_inheritance(self):
+ """ Exercising select_related() with multi-table model inheritance. """
+ c1 = Child.objects.create(name="child1", value=42)
+ i1 = Item.objects.create(name="item1", child=c1)
+ i2 = Item.objects.create(name="item2")
+
+ self.assertQuerysetEqual(
+ Item.objects.select_related("child").order_by("name"),
+ ["<Item: item1>", "<Item: item2>"]
+ )
+
+ def test_regression_12851(self):
+ """
+ Regression for #12851
+
+ Deferred fields are used correctly if you select_related a subset
+ of fields.
+ """
+ australia = Country.objects.create(name='Australia')
+ active = ClientStatus.objects.create(name='active')
+
+ wa = State.objects.create(name="Western Australia", country=australia)
+ c1 = Client.objects.create(name='Brian Burke', state=wa, status=active)
+ burke = Client.objects.select_related('state').defer('state__name').get(name='Brian Burke')
+
+ self.assertEquals(burke.name, u'Brian Burke')
+ self.assertEquals(burke.state.name, u'Western Australia')
+
+ # Still works if we're dealing with an inherited class
+ sc1 = SpecialClient.objects.create(name='Troy Buswell', state=wa, status=active, value=42)
+ troy = SpecialClient.objects.select_related('state').defer('state__name').get(name='Troy Buswell')
+
+ self.assertEquals(troy.name, u'Troy Buswell')
+ self.assertEquals(troy.value, 42)
+ self.assertEquals(troy.state.name, u'Western Australia')
+
+ # Still works if we defer an attribute on the inherited class
+ troy = SpecialClient.objects.select_related('state').defer('value', 'state__name').get(name='Troy Buswell')
+
+ self.assertEquals(troy.name, u'Troy Buswell')
+ self.assertEquals(troy.value, 42)
+ self.assertEquals(troy.state.name, u'Western Australia')
+
+ # Also works if you use only, rather than defer
+ troy = SpecialClient.objects.select_related('state').only('name').get(name='Troy Buswell')
+
+ self.assertEquals(troy.name, u'Troy Buswell')
+ self.assertEquals(troy.value, 42)
+ self.assertEquals(troy.state.name, u'Western Australia')
diff --git a/parts/django/tests/regressiontests/serializers_regress/__init__.py b/parts/django/tests/regressiontests/serializers_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/serializers_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/serializers_regress/models.py b/parts/django/tests/regressiontests/serializers_regress/models.py
new file mode 100644
index 0000000..bec0a98
--- /dev/null
+++ b/parts/django/tests/regressiontests/serializers_regress/models.py
@@ -0,0 +1,266 @@
+"""
+A test spanning all the capabilities of all the serializers.
+
+This class sets up a model for each model field type
+(except for image types, because of the PIL dependency).
+"""
+
+from django.db import models
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.localflavor.us.models import USStateField, PhoneNumberField
+
+# The following classes are for testing basic data
+# marshalling, including NULL values, where allowed.
+
+class BooleanData(models.Model):
+ data = models.BooleanField()
+
+class CharData(models.Model):
+ data = models.CharField(max_length=30, null=True)
+
+class DateData(models.Model):
+ data = models.DateField(null=True)
+
+class DateTimeData(models.Model):
+ data = models.DateTimeField(null=True)
+
+class DecimalData(models.Model):
+ data = models.DecimalField(null=True, decimal_places=3, max_digits=5)
+
+class EmailData(models.Model):
+ data = models.EmailField(null=True)
+
+class FileData(models.Model):
+ data = models.FileField(null=True, upload_to='/foo/bar')
+
+class FilePathData(models.Model):
+ data = models.FilePathField(null=True)
+
+class FloatData(models.Model):
+ data = models.FloatField(null=True)
+
+class IntegerData(models.Model):
+ data = models.IntegerField(null=True)
+
+class BigIntegerData(models.Model):
+ data = models.BigIntegerField(null=True)
+
+# class ImageData(models.Model):
+# data = models.ImageField(null=True)
+
+class IPAddressData(models.Model):
+ data = models.IPAddressField(null=True)
+
+class NullBooleanData(models.Model):
+ data = models.NullBooleanField(null=True)
+
+class PhoneData(models.Model):
+ data = PhoneNumberField(null=True)
+
+class PositiveIntegerData(models.Model):
+ data = models.PositiveIntegerField(null=True)
+
+class PositiveSmallIntegerData(models.Model):
+ data = models.PositiveSmallIntegerField(null=True)
+
+class SlugData(models.Model):
+ data = models.SlugField(null=True)
+
+class SmallData(models.Model):
+ data = models.SmallIntegerField(null=True)
+
+class TextData(models.Model):
+ data = models.TextField(null=True)
+
+class TimeData(models.Model):
+ data = models.TimeField(null=True)
+
+class USStateData(models.Model):
+ data = USStateField(null=True)
+
+class XMLData(models.Model):
+ data = models.XMLField(null=True)
+
+class Tag(models.Model):
+ """A tag on an item."""
+ data = models.SlugField()
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+
+ content_object = generic.GenericForeignKey()
+
+ class Meta:
+ ordering = ["data"]
+
+class GenericData(models.Model):
+ data = models.CharField(max_length=30)
+
+ tags = generic.GenericRelation(Tag)
+
+# The following test classes are all for validation
+# of related objects; in particular, forward, backward,
+# and self references.
+
+class Anchor(models.Model):
+ """This is a model that can be used as
+ something for other models to point at"""
+
+ data = models.CharField(max_length=30)
+
+ class Meta:
+ ordering = ('id',)
+
+class UniqueAnchor(models.Model):
+ """This is a model that can be used as
+ something for other models to point at"""
+
+ data = models.CharField(unique=True, max_length=30)
+
+class FKData(models.Model):
+ data = models.ForeignKey(Anchor, null=True)
+
+class M2MData(models.Model):
+ data = models.ManyToManyField(Anchor, null=True)
+
+class O2OData(models.Model):
+ # One to one field can't be null here, since it is a PK.
+ data = models.OneToOneField(Anchor, primary_key=True)
+
+class FKSelfData(models.Model):
+ data = models.ForeignKey('self', null=True)
+
+class M2MSelfData(models.Model):
+ data = models.ManyToManyField('self', null=True, symmetrical=False)
+
+class FKDataToField(models.Model):
+ data = models.ForeignKey(UniqueAnchor, null=True, to_field='data')
+
+class FKDataToO2O(models.Model):
+ data = models.ForeignKey(O2OData, null=True)
+
+class M2MIntermediateData(models.Model):
+ data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
+
+class Intermediate(models.Model):
+ left = models.ForeignKey(M2MIntermediateData)
+ right = models.ForeignKey(Anchor)
+ extra = models.CharField(max_length=30, blank=True, default="doesn't matter")
+
+# The following test classes are for validating the
+# deserialization of objects that use a user-defined
+# field as the primary key.
+# Some of these data types have been commented out
+# because they can't be used as a primary key on one
+# or all database backends.
+
+class BooleanPKData(models.Model):
+ data = models.BooleanField(primary_key=True)
+
+class CharPKData(models.Model):
+ data = models.CharField(max_length=30, primary_key=True)
+
+# class DatePKData(models.Model):
+# data = models.DateField(primary_key=True)
+
+# class DateTimePKData(models.Model):
+# data = models.DateTimeField(primary_key=True)
+
+class DecimalPKData(models.Model):
+ data = models.DecimalField(primary_key=True, decimal_places=3, max_digits=5)
+
+class EmailPKData(models.Model):
+ data = models.EmailField(primary_key=True)
+
+# class FilePKData(models.Model):
+# data = models.FileField(primary_key=True, upload_to='/foo/bar')
+
+class FilePathPKData(models.Model):
+ data = models.FilePathField(primary_key=True)
+
+class FloatPKData(models.Model):
+ data = models.FloatField(primary_key=True)
+
+class IntegerPKData(models.Model):
+ data = models.IntegerField(primary_key=True)
+
+# class ImagePKData(models.Model):
+# data = models.ImageField(primary_key=True)
+
+class IPAddressPKData(models.Model):
+ data = models.IPAddressField(primary_key=True)
+
+# This is just a Boolean field with null=True, and we can't test a PK value of NULL.
+# class NullBooleanPKData(models.Model):
+# data = models.NullBooleanField(primary_key=True)
+
+class PhonePKData(models.Model):
+ data = PhoneNumberField(primary_key=True)
+
+class PositiveIntegerPKData(models.Model):
+ data = models.PositiveIntegerField(primary_key=True)
+
+class PositiveSmallIntegerPKData(models.Model):
+ data = models.PositiveSmallIntegerField(primary_key=True)
+
+class SlugPKData(models.Model):
+ data = models.SlugField(primary_key=True)
+
+class SmallPKData(models.Model):
+ data = models.SmallIntegerField(primary_key=True)
+
+# class TextPKData(models.Model):
+# data = models.TextField(primary_key=True)
+
+# class TimePKData(models.Model):
+# data = models.TimeField(primary_key=True)
+
+class USStatePKData(models.Model):
+ data = USStateField(primary_key=True)
+
+# class XMLPKData(models.Model):
+# data = models.XMLField(primary_key=True)
+
+class ComplexModel(models.Model):
+ field1 = models.CharField(max_length=10)
+ field2 = models.CharField(max_length=10)
+ field3 = models.CharField(max_length=10)
+
+# Tests for handling fields with pre_save functions, or
+# models with save functions that modify data
+class AutoNowDateTimeData(models.Model):
+ data = models.DateTimeField(null=True, auto_now=True)
+
+class ModifyingSaveData(models.Model):
+ data = models.IntegerField(null=True)
+
+ def save(self):
+ "A save method that modifies the data in the object"
+ self.data = 666
+ super(ModifyingSaveData, self).save(raw)
+
+# Tests for serialization of models using inheritance.
+# Regression for #7202, #7350
+class AbstractBaseModel(models.Model):
+ parent_data = models.IntegerField()
+ class Meta:
+ abstract = True
+
+class InheritAbstractModel(AbstractBaseModel):
+ child_data = models.IntegerField()
+
+class BaseModel(models.Model):
+ parent_data = models.IntegerField()
+
+class InheritBaseModel(BaseModel):
+ child_data = models.IntegerField()
+
+class ExplicitInheritBaseModel(BaseModel):
+ parent = models.OneToOneField(BaseModel)
+ child_data = models.IntegerField()
+
+class LengthModel(models.Model):
+ data = models.IntegerField()
+
+ def __len__(self):
+ return self.data
diff --git a/parts/django/tests/regressiontests/serializers_regress/tests.py b/parts/django/tests/regressiontests/serializers_regress/tests.py
new file mode 100644
index 0000000..be920c6
--- /dev/null
+++ b/parts/django/tests/regressiontests/serializers_regress/tests.py
@@ -0,0 +1,421 @@
+"""
+A test spanning all the capabilities of all the serializers.
+
+This class defines sample data and a dynamically generated
+test case that is capable of testing the capabilities of
+the serializers. This includes all valid data values, plus
+forward, backwards and self references.
+"""
+
+
+import datetime
+import decimal
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from django.conf import settings
+from django.core import serializers, management
+from django.db import transaction, DEFAULT_DB_ALIAS
+from django.test import TestCase
+from django.utils.functional import curry
+
+from models import *
+
+# A set of functions that can be used to recreate
+# test data objects of various kinds.
+# The save method is a raw base model save, to make
+# sure that the data in the database matches the
+# exact test case.
+def data_create(pk, klass, data):
+ instance = klass(id=pk)
+ instance.data = data
+ models.Model.save_base(instance, raw=True)
+ return [instance]
+
+def generic_create(pk, klass, data):
+ instance = klass(id=pk)
+ instance.data = data[0]
+ models.Model.save_base(instance, raw=True)
+ for tag in data[1:]:
+ instance.tags.create(data=tag)
+ return [instance]
+
+def fk_create(pk, klass, data):
+ instance = klass(id=pk)
+ setattr(instance, 'data_id', data)
+ models.Model.save_base(instance, raw=True)
+ return [instance]
+
+def m2m_create(pk, klass, data):
+ instance = klass(id=pk)
+ models.Model.save_base(instance, raw=True)
+ instance.data = data
+ return [instance]
+
+def im2m_create(pk, klass, data):
+ instance = klass(id=pk)
+ models.Model.save_base(instance, raw=True)
+ return [instance]
+
+def im_create(pk, klass, data):
+ instance = klass(id=pk)
+ instance.right_id = data['right']
+ instance.left_id = data['left']
+ if 'extra' in data:
+ instance.extra = data['extra']
+ models.Model.save_base(instance, raw=True)
+ return [instance]
+
+def o2o_create(pk, klass, data):
+ instance = klass()
+ instance.data_id = data
+ models.Model.save_base(instance, raw=True)
+ return [instance]
+
+def pk_create(pk, klass, data):
+ instance = klass()
+ instance.data = data
+ models.Model.save_base(instance, raw=True)
+ return [instance]
+
+def inherited_create(pk, klass, data):
+ instance = klass(id=pk,**data)
+ # This isn't a raw save because:
+ # 1) we're testing inheritance, not field behaviour, so none
+ # of the field values need to be protected.
+ # 2) saving the child class and having the parent created
+ # automatically is easier than manually creating both.
+ models.Model.save(instance)
+ created = [instance]
+ for klass,field in instance._meta.parents.items():
+ created.append(klass.objects.get(id=pk))
+ return created
+
+# A set of functions that can be used to compare
+# test data objects of various kinds
+def data_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data, instance.data,
+ "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % (
+ pk, data, type(data), instance.data, type(instance.data))
+ )
+
+def generic_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data[0], instance.data)
+ testcase.assertEqual(data[1:], [t.data for t in instance.tags.order_by('id')])
+
+def fk_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data, instance.data_id)
+
+def m2m_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data, [obj.id for obj in instance.data.order_by('id')])
+
+def im2m_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ #actually nothing else to check, the instance just should exist
+
+def im_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data['left'], instance.left_id)
+ testcase.assertEqual(data['right'], instance.right_id)
+ if 'extra' in data:
+ testcase.assertEqual(data['extra'], instance.extra)
+ else:
+ testcase.assertEqual("doesn't matter", instance.extra)
+
+def o2o_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(data=data)
+ testcase.assertEqual(data, instance.data_id)
+
+def pk_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(data=data)
+ testcase.assertEqual(data, instance.data)
+
+def inherited_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ for key,value in data.items():
+ testcase.assertEqual(value, getattr(instance,key))
+
+# Define some data types. Each data type is
+# actually a pair of functions; one to create
+# and one to compare objects of that type
+data_obj = (data_create, data_compare)
+generic_obj = (generic_create, generic_compare)
+fk_obj = (fk_create, fk_compare)
+m2m_obj = (m2m_create, m2m_compare)
+im2m_obj = (im2m_create, im2m_compare)
+im_obj = (im_create, im_compare)
+o2o_obj = (o2o_create, o2o_compare)
+pk_obj = (pk_create, pk_compare)
+inherited_obj = (inherited_create, inherited_compare)
+
+test_data = [
+ # Format: (data type, PK value, Model Class, data)
+ (data_obj, 1, BooleanData, True),
+ (data_obj, 2, BooleanData, False),
+ (data_obj, 10, CharData, "Test Char Data"),
+ (data_obj, 11, CharData, ""),
+ (data_obj, 12, CharData, "None"),
+ (data_obj, 13, CharData, "null"),
+ (data_obj, 14, CharData, "NULL"),
+ (data_obj, 15, CharData, None),
+ # (We use something that will fit into a latin1 database encoding here,
+ # because that is still the default used on many system setups.)
+ (data_obj, 16, CharData, u'\xa5'),
+ (data_obj, 20, DateData, datetime.date(2006,6,16)),
+ (data_obj, 21, DateData, None),
+ (data_obj, 30, DateTimeData, datetime.datetime(2006,6,16,10,42,37)),
+ (data_obj, 31, DateTimeData, None),
+ (data_obj, 40, EmailData, "hovercraft@example.com"),
+ (data_obj, 41, EmailData, None),
+ (data_obj, 42, EmailData, ""),
+ (data_obj, 50, FileData, 'file:///foo/bar/whiz.txt'),
+# (data_obj, 51, FileData, None),
+ (data_obj, 52, FileData, ""),
+ (data_obj, 60, FilePathData, "/foo/bar/whiz.txt"),
+ (data_obj, 61, FilePathData, None),
+ (data_obj, 62, FilePathData, ""),
+ (data_obj, 70, DecimalData, decimal.Decimal('12.345')),
+ (data_obj, 71, DecimalData, decimal.Decimal('-12.345')),
+ (data_obj, 72, DecimalData, decimal.Decimal('0.0')),
+ (data_obj, 73, DecimalData, None),
+ (data_obj, 74, FloatData, 12.345),
+ (data_obj, 75, FloatData, -12.345),
+ (data_obj, 76, FloatData, 0.0),
+ (data_obj, 77, FloatData, None),
+ (data_obj, 80, IntegerData, 123456789),
+ (data_obj, 81, IntegerData, -123456789),
+ (data_obj, 82, IntegerData, 0),
+ (data_obj, 83, IntegerData, None),
+ #(XX, ImageData
+ (data_obj, 90, IPAddressData, "127.0.0.1"),
+ (data_obj, 91, IPAddressData, None),
+ (data_obj, 100, NullBooleanData, True),
+ (data_obj, 101, NullBooleanData, False),
+ (data_obj, 102, NullBooleanData, None),
+ (data_obj, 110, PhoneData, "212-634-5789"),
+ (data_obj, 111, PhoneData, None),
+ (data_obj, 120, PositiveIntegerData, 123456789),
+ (data_obj, 121, PositiveIntegerData, None),
+ (data_obj, 130, PositiveSmallIntegerData, 12),
+ (data_obj, 131, PositiveSmallIntegerData, None),
+ (data_obj, 140, SlugData, "this-is-a-slug"),
+ (data_obj, 141, SlugData, None),
+ (data_obj, 142, SlugData, ""),
+ (data_obj, 150, SmallData, 12),
+ (data_obj, 151, SmallData, -12),
+ (data_obj, 152, SmallData, 0),
+ (data_obj, 153, SmallData, None),
+ (data_obj, 160, TextData, """This is a long piece of text.
+It contains line breaks.
+Several of them.
+The end."""),
+ (data_obj, 161, TextData, ""),
+ (data_obj, 162, TextData, None),
+ (data_obj, 170, TimeData, datetime.time(10,42,37)),
+ (data_obj, 171, TimeData, None),
+ (data_obj, 180, USStateData, "MA"),
+ (data_obj, 181, USStateData, None),
+ (data_obj, 182, USStateData, ""),
+ (data_obj, 190, XMLData, "<foo></foo>"),
+ (data_obj, 191, XMLData, None),
+ (data_obj, 192, XMLData, ""),
+
+ (generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']),
+ (generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']),
+
+ (data_obj, 300, Anchor, "Anchor 1"),
+ (data_obj, 301, Anchor, "Anchor 2"),
+ (data_obj, 302, UniqueAnchor, "UAnchor 1"),
+
+ (fk_obj, 400, FKData, 300), # Post reference
+ (fk_obj, 401, FKData, 500), # Pre reference
+ (fk_obj, 402, FKData, None), # Empty reference
+
+ (m2m_obj, 410, M2MData, []), # Empty set
+ (m2m_obj, 411, M2MData, [300,301]), # Post reference
+ (m2m_obj, 412, M2MData, [500,501]), # Pre reference
+ (m2m_obj, 413, M2MData, [300,301,500,501]), # Pre and Post reference
+
+ (o2o_obj, None, O2OData, 300), # Post reference
+ (o2o_obj, None, O2OData, 500), # Pre reference
+
+ (fk_obj, 430, FKSelfData, 431), # Pre reference
+ (fk_obj, 431, FKSelfData, 430), # Post reference
+ (fk_obj, 432, FKSelfData, None), # Empty reference
+
+ (m2m_obj, 440, M2MSelfData, []),
+ (m2m_obj, 441, M2MSelfData, []),
+ (m2m_obj, 442, M2MSelfData, [440, 441]),
+ (m2m_obj, 443, M2MSelfData, [445, 446]),
+ (m2m_obj, 444, M2MSelfData, [440, 441, 445, 446]),
+ (m2m_obj, 445, M2MSelfData, []),
+ (m2m_obj, 446, M2MSelfData, []),
+
+ (fk_obj, 450, FKDataToField, "UAnchor 1"),
+ (fk_obj, 451, FKDataToField, "UAnchor 2"),
+ (fk_obj, 452, FKDataToField, None),
+
+ (fk_obj, 460, FKDataToO2O, 300),
+
+ (im2m_obj, 470, M2MIntermediateData, None),
+
+ #testing post- and prereferences and extra fields
+ (im_obj, 480, Intermediate, {'right': 300, 'left': 470}),
+ (im_obj, 481, Intermediate, {'right': 300, 'left': 490}),
+ (im_obj, 482, Intermediate, {'right': 500, 'left': 470}),
+ (im_obj, 483, Intermediate, {'right': 500, 'left': 490}),
+ (im_obj, 484, Intermediate, {'right': 300, 'left': 470, 'extra': "extra"}),
+ (im_obj, 485, Intermediate, {'right': 300, 'left': 490, 'extra': "extra"}),
+ (im_obj, 486, Intermediate, {'right': 500, 'left': 470, 'extra': "extra"}),
+ (im_obj, 487, Intermediate, {'right': 500, 'left': 490, 'extra': "extra"}),
+
+ (im2m_obj, 490, M2MIntermediateData, []),
+
+ (data_obj, 500, Anchor, "Anchor 3"),
+ (data_obj, 501, Anchor, "Anchor 4"),
+ (data_obj, 502, UniqueAnchor, "UAnchor 2"),
+
+ (pk_obj, 601, BooleanPKData, True),
+ (pk_obj, 602, BooleanPKData, False),
+ (pk_obj, 610, CharPKData, "Test Char PKData"),
+# (pk_obj, 620, DatePKData, datetime.date(2006,6,16)),
+# (pk_obj, 630, DateTimePKData, datetime.datetime(2006,6,16,10,42,37)),
+ (pk_obj, 640, EmailPKData, "hovercraft@example.com"),
+# (pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'),
+ (pk_obj, 660, FilePathPKData, "/foo/bar/whiz.txt"),
+ (pk_obj, 670, DecimalPKData, decimal.Decimal('12.345')),
+ (pk_obj, 671, DecimalPKData, decimal.Decimal('-12.345')),
+ (pk_obj, 672, DecimalPKData, decimal.Decimal('0.0')),
+ (pk_obj, 673, FloatPKData, 12.345),
+ (pk_obj, 674, FloatPKData, -12.345),
+ (pk_obj, 675, FloatPKData, 0.0),
+ (pk_obj, 680, IntegerPKData, 123456789),
+ (pk_obj, 681, IntegerPKData, -123456789),
+ (pk_obj, 682, IntegerPKData, 0),
+# (XX, ImagePKData
+ (pk_obj, 690, IPAddressPKData, "127.0.0.1"),
+ # (pk_obj, 700, NullBooleanPKData, True),
+ # (pk_obj, 701, NullBooleanPKData, False),
+ (pk_obj, 710, PhonePKData, "212-634-5789"),
+ (pk_obj, 720, PositiveIntegerPKData, 123456789),
+ (pk_obj, 730, PositiveSmallIntegerPKData, 12),
+ (pk_obj, 740, SlugPKData, "this-is-a-slug"),
+ (pk_obj, 750, SmallPKData, 12),
+ (pk_obj, 751, SmallPKData, -12),
+ (pk_obj, 752, SmallPKData, 0),
+# (pk_obj, 760, TextPKData, """This is a long piece of text.
+# It contains line breaks.
+# Several of them.
+# The end."""),
+# (pk_obj, 770, TimePKData, datetime.time(10,42,37)),
+ (pk_obj, 780, USStatePKData, "MA"),
+# (pk_obj, 790, XMLPKData, "<foo></foo>"),
+
+ (data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
+ (data_obj, 810, ModifyingSaveData, 42),
+
+ (inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}),
+ (inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}),
+ (inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}),
+
+ (data_obj, 1000, BigIntegerData, 9223372036854775807),
+ (data_obj, 1001, BigIntegerData, -9223372036854775808),
+ (data_obj, 1002, BigIntegerData, 0),
+ (data_obj, 1003, BigIntegerData, None),
+ (data_obj, 1004, LengthModel, 0),
+ (data_obj, 1005, LengthModel, 1),
+]
+
+# Because Oracle treats the empty string as NULL, Oracle is expected to fail
+# when field.empty_strings_allowed is True and the value is None; skip these
+# tests.
+if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+ test_data = [data for data in test_data
+ if not (data[0] == data_obj and
+ data[2]._meta.get_field('data').empty_strings_allowed and
+ data[3] is None)]
+
+# Regression test for #8651 -- a FK to an object iwth PK of 0
+# This won't work on MySQL since it won't let you create an object
+# with a primary key of 0,
+if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql':
+ test_data.extend([
+ (data_obj, 0, Anchor, "Anchor 0"),
+ (fk_obj, 465, FKData, 0),
+ ])
+
+# Dynamically create serializer tests to ensure that all
+# registered serializers are automatically tested.
+class SerializerTests(TestCase):
+ pass
+
+def serializerTest(format, self):
+
+ # Create all the objects defined in the test data
+ objects = []
+ instance_count = {}
+ for (func, pk, klass, datum) in test_data:
+ objects.extend(func[0](pk, klass, datum))
+
+ # Get a count of the number of objects created for each class
+ for klass in instance_count:
+ instance_count[klass] = klass.objects.count()
+
+ # Add the generic tagged objects to the object list
+ objects.extend(Tag.objects.all())
+
+ # Serialize the test database
+ serialized_data = serializers.serialize(format, objects, indent=2)
+
+ for obj in serializers.deserialize(format, serialized_data):
+ obj.save()
+
+ # Assert that the deserialized data is the same
+ # as the original source
+ for (func, pk, klass, datum) in test_data:
+ func[1](self, pk, klass, datum)
+
+ # Assert that the number of objects deserialized is the
+ # same as the number that was serialized.
+ for klass, count in instance_count.items():
+ self.assertEquals(count, klass.objects.count())
+
+def fieldsTest(format, self):
+ obj = ComplexModel(field1='first', field2='second', field3='third')
+ obj.save_base(raw=True)
+
+ # Serialize then deserialize the test database
+ serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3'))
+ result = serializers.deserialize(format, serialized_data).next()
+
+ # Check that the deserialized object contains data in only the serialized fields.
+ self.assertEqual(result.object.field1, 'first')
+ self.assertEqual(result.object.field2, '')
+ self.assertEqual(result.object.field3, 'third')
+
+def streamTest(format, self):
+ obj = ComplexModel(field1='first',field2='second',field3='third')
+ obj.save_base(raw=True)
+
+ # Serialize the test database to a stream
+ stream = StringIO()
+ serializers.serialize(format, [obj], indent=2, stream=stream)
+
+ # Serialize normally for a comparison
+ string_data = serializers.serialize(format, [obj], indent=2)
+
+ # Check that the two are the same
+ self.assertEqual(string_data, stream.getvalue())
+ stream.close()
+
+for format in serializers.get_serializer_formats():
+ setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format))
+ setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format))
+ if format != 'python':
+ setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format))
diff --git a/parts/django/tests/regressiontests/servers/__init__.py b/parts/django/tests/regressiontests/servers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/servers/__init__.py
diff --git a/parts/django/tests/regressiontests/servers/models.py b/parts/django/tests/regressiontests/servers/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/servers/models.py
diff --git a/parts/django/tests/regressiontests/servers/tests.py b/parts/django/tests/regressiontests/servers/tests.py
new file mode 100644
index 0000000..4763982
--- /dev/null
+++ b/parts/django/tests/regressiontests/servers/tests.py
@@ -0,0 +1,68 @@
+"""
+Tests for django.core.servers.
+"""
+
+import os
+
+import django
+from django.test import TestCase
+from django.core.handlers.wsgi import WSGIHandler
+from django.core.servers.basehttp import AdminMediaHandler
+
+
+class AdminMediaHandlerTests(TestCase):
+
+ def setUp(self):
+ self.admin_media_file_path = os.path.abspath(
+ os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
+ )
+ self.handler = AdminMediaHandler(WSGIHandler())
+
+ def test_media_urls(self):
+ """
+ Tests that URLs that look like absolute file paths after the
+ settings.ADMIN_MEDIA_PREFIX don't turn into absolute file paths.
+ """
+ # Cases that should work on all platforms.
+ data = (
+ ('/media/css/base.css', ('css', 'base.css')),
+ )
+ # Cases that should raise an exception.
+ bad_data = ()
+
+ # Add platform-specific cases.
+ if os.sep == '/':
+ data += (
+ # URL, tuple of relative path parts.
+ ('/media/\\css/base.css', ('\\css', 'base.css')),
+ )
+ bad_data += (
+ '/media//css/base.css',
+ '/media////css/base.css',
+ '/media/../css/base.css',
+ )
+ elif os.sep == '\\':
+ bad_data += (
+ '/media/C:\css/base.css',
+ '/media//\\css/base.css',
+ '/media/\\css/base.css',
+ '/media/\\\\css/base.css'
+ )
+ for url, path_tuple in data:
+ try:
+ output = self.handler.file_path(url)
+ except ValueError:
+ self.fail("Got a ValueError exception, but wasn't expecting"
+ " one. URL was: %s" % url)
+ rel_path = os.path.join(*path_tuple)
+ desired = os.path.normcase(
+ os.path.join(self.admin_media_file_path, rel_path))
+ self.assertEqual(output, desired,
+ "Got: %s, Expected: %s, URL was: %s" % (output, desired, url))
+ for url in bad_data:
+ try:
+ output = self.handler.file_path(url)
+ except ValueError:
+ continue
+ self.fail('URL: %s should have caused a ValueError exception.'
+ % url)
diff --git a/parts/django/tests/regressiontests/settings_tests/__init__.py b/parts/django/tests/regressiontests/settings_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/settings_tests/__init__.py
diff --git a/parts/django/tests/regressiontests/settings_tests/models.py b/parts/django/tests/regressiontests/settings_tests/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/settings_tests/models.py
diff --git a/parts/django/tests/regressiontests/settings_tests/tests.py b/parts/django/tests/regressiontests/settings_tests/tests.py
new file mode 100644
index 0000000..fa217b1
--- /dev/null
+++ b/parts/django/tests/regressiontests/settings_tests/tests.py
@@ -0,0 +1,17 @@
+import unittest
+from django.conf import settings
+
+class SettingsTests(unittest.TestCase):
+
+ #
+ # Regression tests for #10130: deleting settings.
+ #
+
+ def test_settings_delete(self):
+ settings.TEST = 'test'
+ self.assertEqual('test', settings.TEST)
+ del settings.TEST
+ self.assertRaises(AttributeError, getattr, settings, 'TEST')
+
+ def test_settings_delete_wrapped(self):
+ self.assertRaises(TypeError, delattr, settings, '_wrapped')
diff --git a/parts/django/tests/regressiontests/signals_regress/__init__.py b/parts/django/tests/regressiontests/signals_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/signals_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/signals_regress/models.py b/parts/django/tests/regressiontests/signals_regress/models.py
new file mode 100644
index 0000000..e7879d8
--- /dev/null
+++ b/parts/django/tests/regressiontests/signals_regress/models.py
@@ -0,0 +1,14 @@
+from django.db import models
+
+class Author(models.Model):
+ name = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return self.name
+
+class Book(models.Model):
+ name = models.CharField(max_length=20)
+ authors = models.ManyToManyField(Author)
+
+ def __unicode__(self):
+ return self.name
diff --git a/parts/django/tests/regressiontests/signals_regress/tests.py b/parts/django/tests/regressiontests/signals_regress/tests.py
new file mode 100644
index 0000000..234893f
--- /dev/null
+++ b/parts/django/tests/regressiontests/signals_regress/tests.py
@@ -0,0 +1,96 @@
+import sys
+from StringIO import StringIO
+from django.test import TestCase
+
+from django.db import models
+from regressiontests.signals_regress.models import Author, Book
+
+signal_output = []
+
+def pre_save_test(signal, sender, instance, **kwargs):
+ signal_output.append('pre_save signal, %s' % instance)
+ if kwargs.get('raw'):
+ signal_output.append('Is raw')
+
+def post_save_test(signal, sender, instance, **kwargs):
+ signal_output.append('post_save signal, %s' % instance)
+ if 'created' in kwargs:
+ if kwargs['created']:
+ signal_output.append('Is created')
+ else:
+ signal_output.append('Is updated')
+ if kwargs.get('raw'):
+ signal_output.append('Is raw')
+
+def pre_delete_test(signal, sender, instance, **kwargs):
+ signal_output.append('pre_save signal, %s' % instance)
+ signal_output.append('instance.id is not None: %s' % (instance.id != None))
+
+def post_delete_test(signal, sender, instance, **kwargs):
+ signal_output.append('post_delete signal, %s' % instance)
+ signal_output.append('instance.id is not None: %s' % (instance.id != None))
+
+class SignalsRegressTests(TestCase):
+ """
+ Testing signals before/after saving and deleting.
+ """
+
+ def get_signal_output(self, fn, *args, **kwargs):
+ # Flush any existing signal output
+ global signal_output
+ signal_output = []
+ fn(*args, **kwargs)
+ return signal_output
+
+ def setUp(self):
+ # Save up the number of connected signals so that we can check at the end
+ # that all the signals we register get properly unregistered (#9989)
+ self.pre_signals = (len(models.signals.pre_save.receivers),
+ len(models.signals.post_save.receivers),
+ len(models.signals.pre_delete.receivers),
+ len(models.signals.post_delete.receivers))
+
+ models.signals.pre_save.connect(pre_save_test)
+ models.signals.post_save.connect(post_save_test)
+ models.signals.pre_delete.connect(pre_delete_test)
+ models.signals.post_delete.connect(post_delete_test)
+
+ def tearDown(self):
+ models.signals.post_delete.disconnect(post_delete_test)
+ models.signals.pre_delete.disconnect(pre_delete_test)
+ models.signals.post_save.disconnect(post_save_test)
+ models.signals.pre_save.disconnect(pre_save_test)
+
+ # Check that all our signals got disconnected properly.
+ post_signals = (len(models.signals.pre_save.receivers),
+ len(models.signals.post_save.receivers),
+ len(models.signals.pre_delete.receivers),
+ len(models.signals.post_delete.receivers))
+
+ self.assertEquals(self.pre_signals, post_signals)
+
+ def test_model_signals(self):
+ """ Model saves should throw some signals. """
+ a1 = Author(name='Neal Stephenson')
+ self.assertEquals(self.get_signal_output(a1.save), [
+ "pre_save signal, Neal Stephenson",
+ "post_save signal, Neal Stephenson",
+ "Is created"
+ ])
+
+ b1 = Book(name='Snow Crash')
+ self.assertEquals(self.get_signal_output(b1.save), [
+ "pre_save signal, Snow Crash",
+ "post_save signal, Snow Crash",
+ "Is created"
+ ])
+
+ def test_m2m_signals(self):
+ """ Assigning and removing to/from m2m shouldn't generate an m2m signal """
+
+ b1 = Book(name='Snow Crash')
+ self.get_signal_output(b1.save)
+ a1 = Author(name='Neal Stephenson')
+ self.get_signal_output(a1.save)
+ self.assertEquals(self.get_signal_output(setattr, b1, 'authors', [a1]), [])
+ self.assertEquals(self.get_signal_output(setattr, b1, 'authors', []), [])
diff --git a/parts/django/tests/regressiontests/sites_framework/__init__.py b/parts/django/tests/regressiontests/sites_framework/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/sites_framework/__init__.py
diff --git a/parts/django/tests/regressiontests/sites_framework/models.py b/parts/django/tests/regressiontests/sites_framework/models.py
new file mode 100644
index 0000000..9ecc3e6
--- /dev/null
+++ b/parts/django/tests/regressiontests/sites_framework/models.py
@@ -0,0 +1,36 @@
+from django.contrib.sites.managers import CurrentSiteManager
+from django.contrib.sites.models import Site
+from django.db import models
+
+class AbstractArticle(models.Model):
+ title = models.CharField(max_length=50)
+
+ objects = models.Manager()
+ on_site = CurrentSiteManager()
+
+ class Meta:
+ abstract = True
+
+ def __unicode__(self):
+ return self.title
+
+class SyndicatedArticle(AbstractArticle):
+ sites = models.ManyToManyField(Site)
+
+class ExclusiveArticle(AbstractArticle):
+ site = models.ForeignKey(Site)
+
+class CustomArticle(AbstractArticle):
+ places_this_article_should_appear = models.ForeignKey(Site)
+
+ objects = models.Manager()
+ on_site = CurrentSiteManager("places_this_article_should_appear")
+
+class InvalidArticle(AbstractArticle):
+ site = models.ForeignKey(Site)
+
+ objects = models.Manager()
+ on_site = CurrentSiteManager("places_this_article_should_appear")
+
+class ConfusedArticle(AbstractArticle):
+ site = models.IntegerField()
diff --git a/parts/django/tests/regressiontests/sites_framework/tests.py b/parts/django/tests/regressiontests/sites_framework/tests.py
new file mode 100644
index 0000000..b737727
--- /dev/null
+++ b/parts/django/tests/regressiontests/sites_framework/tests.py
@@ -0,0 +1,34 @@
+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.test import TestCase
+
+from models import SyndicatedArticle, ExclusiveArticle, CustomArticle, InvalidArticle, ConfusedArticle
+
+class SitesFrameworkTestCase(TestCase):
+ def setUp(self):
+ Site.objects.get_or_create(id=settings.SITE_ID, domain="example.com", name="example.com")
+ Site.objects.create(id=settings.SITE_ID+1, domain="example2.com", name="example2.com")
+
+ def test_site_fk(self):
+ article = ExclusiveArticle.objects.create(title="Breaking News!", site_id=settings.SITE_ID)
+ self.assertEqual(ExclusiveArticle.on_site.all().get(), article)
+
+ def test_sites_m2m(self):
+ article = SyndicatedArticle.objects.create(title="Fresh News!")
+ article.sites.add(Site.objects.get(id=settings.SITE_ID))
+ article.sites.add(Site.objects.get(id=settings.SITE_ID+1))
+ article2 = SyndicatedArticle.objects.create(title="More News!")
+ article2.sites.add(Site.objects.get(id=settings.SITE_ID+1))
+ self.assertEqual(SyndicatedArticle.on_site.all().get(), article)
+
+ def test_custom_named_field(self):
+ article = CustomArticle.objects.create(title="Tantalizing News!", places_this_article_should_appear_id=settings.SITE_ID)
+ self.assertEqual(CustomArticle.on_site.all().get(), article)
+
+ def test_invalid_name(self):
+ article = InvalidArticle.objects.create(title="Bad News!", site_id=settings.SITE_ID)
+ self.assertRaises(ValueError, InvalidArticle.on_site.all)
+
+ def test_invalid_field_type(self):
+ article = ConfusedArticle.objects.create(title="More Bad News!", site=settings.SITE_ID)
+ self.assertRaises(TypeError, ConfusedArticle.on_site.all)
diff --git a/parts/django/tests/regressiontests/special_headers/__init__.py b/parts/django/tests/regressiontests/special_headers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/__init__.py
diff --git a/parts/django/tests/regressiontests/special_headers/fixtures/data.xml b/parts/django/tests/regressiontests/special_headers/fixtures/data.xml
new file mode 100644
index 0000000..7e60d45
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/fixtures/data.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+ <object pk="1" model="special_headers.article">
+ <field type="TextField" name="text">text</field>
+ </object>
+</django-objects>
diff --git a/parts/django/tests/regressiontests/special_headers/models.py b/parts/django/tests/regressiontests/special_headers/models.py
new file mode 100644
index 0000000..0c12675
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/models.py
@@ -0,0 +1,4 @@
+from django.db import models
+
+class Article(models.Model):
+ text = models.TextField()
diff --git a/parts/django/tests/regressiontests/special_headers/templates/special_headers/article_detail.html b/parts/django/tests/regressiontests/special_headers/templates/special_headers/article_detail.html
new file mode 100644
index 0000000..3cbd38c
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/templates/special_headers/article_detail.html
@@ -0,0 +1 @@
+{{ object }}
diff --git a/parts/django/tests/regressiontests/special_headers/tests.py b/parts/django/tests/regressiontests/special_headers/tests.py
new file mode 100644
index 0000000..f304bfa
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/tests.py
@@ -0,0 +1,40 @@
+from django.test import TestCase
+from django.contrib.auth.models import User
+
+class SpecialHeadersTest(TestCase):
+ fixtures = ['data.xml']
+
+ def test_xheaders(self):
+ user = User.objects.get(username='super')
+ response = self.client.get('/special_headers/article/1/')
+ # import pdb; pdb.set_trace()
+ self.failUnless('X-Object-Type' not in response)
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/special_headers/article/1/')
+ self.failUnless('X-Object-Type' in response)
+ user.is_staff = False
+ user.save()
+ response = self.client.get('/special_headers/article/1/')
+ self.failUnless('X-Object-Type' not in response)
+ user.is_staff = True
+ user.is_active = False
+ user.save()
+ response = self.client.get('/special_headers/article/1/')
+ self.failUnless('X-Object-Type' not in response)
+
+ def test_xview(self):
+ user = User.objects.get(username='super')
+ response = self.client.head('/special_headers/xview/')
+ self.failUnless('X-View' not in response)
+ self.client.login(username='super', password='secret')
+ response = self.client.head('/special_headers/xview/')
+ self.failUnless('X-View' in response)
+ user.is_staff = False
+ user.save()
+ response = self.client.head('/special_headers/xview/')
+ self.failUnless('X-View' not in response)
+ user.is_staff = True
+ user.is_active = False
+ user.save()
+ response = self.client.head('/special_headers/xview/')
+ self.failUnless('X-View' not in response)
diff --git a/parts/django/tests/regressiontests/special_headers/urls.py b/parts/django/tests/regressiontests/special_headers/urls.py
new file mode 100644
index 0000000..721f60a
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/urls.py
@@ -0,0 +1,10 @@
+# coding: utf-8
+from django.conf.urls.defaults import *
+from django.views.generic.list_detail import object_detail
+from models import Article
+import views
+
+urlpatterns = patterns('',
+ (r'^article/(?P<object_id>\d+)/$', object_detail, {'queryset': Article.objects.all()}),
+ (r'^xview/$', views.xview),
+)
diff --git a/parts/django/tests/regressiontests/special_headers/views.py b/parts/django/tests/regressiontests/special_headers/views.py
new file mode 100644
index 0000000..7a01203
--- /dev/null
+++ b/parts/django/tests/regressiontests/special_headers/views.py
@@ -0,0 +1,10 @@
+# -*- coding:utf-8 -*-
+from django.http import HttpResponse
+from django.utils.decorators import decorator_from_middleware
+from django.middleware.doc import XViewMiddleware
+
+xview_dec = decorator_from_middleware(XViewMiddleware)
+
+def xview(request):
+ return HttpResponse()
+xview = xview_dec(xview)
diff --git a/parts/django/tests/regressiontests/string_lookup/__init__.py b/parts/django/tests/regressiontests/string_lookup/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/string_lookup/__init__.py
diff --git a/parts/django/tests/regressiontests/string_lookup/models.py b/parts/django/tests/regressiontests/string_lookup/models.py
new file mode 100644
index 0000000..037854d
--- /dev/null
+++ b/parts/django/tests/regressiontests/string_lookup/models.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from django.db import models
+
+class Foo(models.Model):
+ name = models.CharField(max_length=50)
+ friend = models.CharField(max_length=50, blank=True)
+
+ def __unicode__(self):
+ return "Foo %s" % self.name
+
+class Bar(models.Model):
+ name = models.CharField(max_length=50)
+ normal = models.ForeignKey(Foo, related_name='normal_foo')
+ fwd = models.ForeignKey("Whiz")
+ back = models.ForeignKey("Foo")
+
+ def __unicode__(self):
+ return "Bar %s" % self.place.name
+
+class Whiz(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return "Whiz %s" % self.name
+
+class Child(models.Model):
+ parent = models.OneToOneField('Base')
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return "Child %s" % self.name
+
+class Base(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return "Base %s" % self.name
+
+class Article(models.Model):
+ name = models.CharField(max_length=50)
+ text = models.TextField()
+ submitted_from = models.IPAddressField(blank=True, null=True)
+
+ def __str__(self):
+ return "Article %s" % self.name
diff --git a/parts/django/tests/regressiontests/string_lookup/tests.py b/parts/django/tests/regressiontests/string_lookup/tests.py
new file mode 100644
index 0000000..ddf7a8a
--- /dev/null
+++ b/parts/django/tests/regressiontests/string_lookup/tests.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+from django.test import TestCase
+from regressiontests.string_lookup.models import Foo, Whiz, Bar, Article, Base, Child
+
+class StringLookupTests(TestCase):
+
+ def test_string_form_referencing(self):
+ """
+ Regression test for #1661 and #1662
+
+ Check that string form referencing of
+ models works, both as pre and post reference, on all RelatedField types.
+ """
+
+ f1 = Foo(name="Foo1")
+ f1.save()
+ f2 = Foo(name="Foo2")
+ f2.save()
+
+ w1 = Whiz(name="Whiz1")
+ w1.save()
+
+ b1 = Bar(name="Bar1", normal=f1, fwd=w1, back=f2)
+ b1.save()
+
+ self.assertEquals(b1.normal, f1)
+
+ self.assertEquals(b1.fwd, w1)
+
+ self.assertEquals(b1.back, f2)
+
+ base1 = Base(name="Base1")
+ base1.save()
+
+ child1 = Child(name="Child1", parent=base1)
+ child1.save()
+
+ self.assertEquals(child1.parent, base1)
+
+ def test_unicode_chars_in_queries(self):
+ """
+ Regression tests for #3937
+
+ make sure we can use unicode characters in queries.
+ If these tests fail on MySQL, it's a problem with the test setup.
+ A properly configured UTF-8 database can handle this.
+ """
+
+ fx = Foo(name='Bjorn', friend=u'François')
+ fx.save()
+ self.assertEquals(Foo.objects.get(friend__contains=u'\xe7'), fx)
+
+ # We can also do the above query using UTF-8 strings.
+ self.assertEquals(Foo.objects.get(friend__contains='\xc3\xa7'), fx)
+
+ def test_queries_on_textfields(self):
+ """
+ Regression tests for #5087
+
+ make sure we can perform queries on TextFields.
+ """
+
+ a = Article(name='Test', text='The quick brown fox jumps over the lazy dog.')
+ a.save()
+ self.assertEquals(Article.objects.get(text__exact='The quick brown fox jumps over the lazy dog.'), a)
+
+ self.assertEquals(Article.objects.get(text__contains='quick brown fox'), a)
+
+ def test_ipaddress_on_postgresql(self):
+ """
+ Regression test for #708
+
+ "like" queries on IP address fields require casting to text (on PostgreSQL).
+ """
+ a = Article(name='IP test', text='The body', submitted_from='192.0.2.100')
+ a.save()
+ self.assertEquals(repr(Article.objects.filter(submitted_from__contains='192.0.2')),
+ repr([a]))
diff --git a/parts/django/tests/regressiontests/syndication/__init__.py b/parts/django/tests/regressiontests/syndication/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/__init__.py
diff --git a/parts/django/tests/regressiontests/syndication/feeds.py b/parts/django/tests/regressiontests/syndication/feeds.py
new file mode 100644
index 0000000..5563170
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/feeds.py
@@ -0,0 +1,142 @@
+from django.contrib.syndication import feeds, views
+from django.core.exceptions import ObjectDoesNotExist
+from django.utils import feedgenerator, tzinfo
+from models import Article, Entry
+
+
+class ComplexFeed(views.Feed):
+ def get_object(self, request, foo=None):
+ if foo is not None:
+ raise ObjectDoesNotExist
+ return None
+
+
+class TestRss2Feed(views.Feed):
+ title = 'My blog'
+ description = 'A more thorough description of my blog.'
+ link = '/blog/'
+ feed_guid = '/foo/bar/1234'
+ author_name = 'Sally Smith'
+ author_email = 'test@example.com'
+ author_link = 'http://www.example.com/'
+ categories = ('python', 'django')
+ feed_copyright = 'Copyright (c) 2007, Sally Smith'
+ ttl = 600
+
+ def items(self):
+ return Entry.objects.all()
+
+ def item_description(self, item):
+ return "Overridden description: %s" % item
+
+ def item_pubdate(self, item):
+ return item.date
+
+ item_author_name = 'Sally Smith'
+ item_author_email = 'test@example.com'
+ item_author_link = 'http://www.example.com/'
+ item_categories = ('python', 'testing')
+ item_copyright = 'Copyright (c) 2007, Sally Smith'
+
+
+class TestRss091Feed(TestRss2Feed):
+ feed_type = feedgenerator.RssUserland091Feed
+
+
+class TestAtomFeed(TestRss2Feed):
+ feed_type = feedgenerator.Atom1Feed
+ subtitle = TestRss2Feed.description
+
+
+class ArticlesFeed(TestRss2Feed):
+ """
+ A feed to test no link being defined. Articles have no get_absolute_url()
+ method, and item_link() is not defined.
+ """
+ def items(self):
+ return Article.objects.all()
+
+
+class TestEnclosureFeed(TestRss2Feed):
+ pass
+
+
+class TemplateFeed(TestRss2Feed):
+ """
+ A feed to test defining item titles and descriptions with templates.
+ """
+ title_template = 'syndication/title.html'
+ description_template = 'syndication/description.html'
+
+ # Defining a template overrides any item_title definition
+ def item_title(self):
+ return "Not in a template"
+
+
+class NaiveDatesFeed(TestAtomFeed):
+ """
+ A feed with naive (non-timezone-aware) dates.
+ """
+ def item_pubdate(self, item):
+ return item.date
+
+
+class TZAwareDatesFeed(TestAtomFeed):
+ """
+ A feed with timezone-aware dates.
+ """
+ def item_pubdate(self, item):
+ # Provide a weird offset so that the test can know it's getting this
+ # specific offset and not accidentally getting on from
+ # settings.TIME_ZONE.
+ return item.date.replace(tzinfo=tzinfo.FixedOffset(42))
+
+
+class TestFeedUrlFeed(TestAtomFeed):
+ feed_url = 'http://example.com/customfeedurl/'
+
+
+class MyCustomAtom1Feed(feedgenerator.Atom1Feed):
+ """
+ Test of a custom feed generator class.
+ """
+ def root_attributes(self):
+ attrs = super(MyCustomAtom1Feed, self).root_attributes()
+ attrs[u'django'] = u'rocks'
+ return attrs
+
+ def add_root_elements(self, handler):
+ super(MyCustomAtom1Feed, self).add_root_elements(handler)
+ handler.addQuickElement(u'spam', u'eggs')
+
+ def item_attributes(self, item):
+ attrs = super(MyCustomAtom1Feed, self).item_attributes(item)
+ attrs[u'bacon'] = u'yum'
+ return attrs
+
+ def add_item_elements(self, handler, item):
+ super(MyCustomAtom1Feed, self).add_item_elements(handler, item)
+ handler.addQuickElement(u'ministry', u'silly walks')
+
+
+class TestCustomFeed(TestAtomFeed):
+ feed_type = MyCustomAtom1Feed
+
+
+class DeprecatedComplexFeed(feeds.Feed):
+ def get_object(self, bits):
+ if len(bits) != 1:
+ raise ObjectDoesNotExist
+ return None
+
+
+class DeprecatedRssFeed(feeds.Feed):
+ link = "/blog/"
+ title = 'My blog'
+
+ def items(self):
+ return Entry.objects.all()
+
+ def item_link(self, item):
+ return "/blog/%s/" % item.pk
+
diff --git a/parts/django/tests/regressiontests/syndication/fixtures/feeddata.json b/parts/django/tests/regressiontests/syndication/fixtures/feeddata.json
new file mode 100644
index 0000000..4a5c022
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/fixtures/feeddata.json
@@ -0,0 +1,42 @@
+[
+ {
+ "model": "syndication.entry",
+ "pk": 1,
+ "fields": {
+ "title": "My first entry",
+ "date": "2008-01-01 12:30:00"
+ }
+ },
+ {
+ "model": "syndication.entry",
+ "pk": 2,
+ "fields": {
+ "title": "My second entry",
+ "date": "2008-01-02 12:30:00"
+ }
+ },
+ {
+ "model": "syndication.entry",
+ "pk": 3,
+ "fields": {
+ "title": "My third entry",
+ "date": "2008-01-02 13:30:00"
+ }
+ },
+ {
+ "model": "syndication.entry",
+ "pk": 4,
+ "fields": {
+ "title": "A & B < C > D",
+ "date": "2008-01-03 13:30:00"
+ }
+ },
+ {
+ "model": "syndication.article",
+ "pk": 1,
+ "fields": {
+ "title": "My first article",
+ "entry": "1"
+ }
+ }
+]
diff --git a/parts/django/tests/regressiontests/syndication/models.py b/parts/django/tests/regressiontests/syndication/models.py
new file mode 100644
index 0000000..54230b9
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/models.py
@@ -0,0 +1,23 @@
+from django.db import models
+
+class Entry(models.Model):
+ title = models.CharField(max_length=200)
+ date = models.DateTimeField()
+
+ class Meta:
+ ordering = ('date',)
+
+ def __unicode__(self):
+ return self.title
+
+ def get_absolute_url(self):
+ return "/blog/%s/" % self.pk
+
+
+class Article(models.Model):
+ title = models.CharField(max_length=200)
+ entry = models.ForeignKey(Entry)
+
+ def __unicode__(self):
+ return self.title
+
diff --git a/parts/django/tests/regressiontests/syndication/templates/syndication/description.html b/parts/django/tests/regressiontests/syndication/templates/syndication/description.html
new file mode 100644
index 0000000..85ec82c
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/templates/syndication/description.html
@@ -0,0 +1 @@
+Description in your templates: {{ obj }} \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/syndication/templates/syndication/title.html b/parts/django/tests/regressiontests/syndication/templates/syndication/title.html
new file mode 100644
index 0000000..eb17969
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/templates/syndication/title.html
@@ -0,0 +1 @@
+Title in your templates: {{ obj }} \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/syndication/tests.py b/parts/django/tests/regressiontests/syndication/tests.py
new file mode 100644
index 0000000..76a6c88
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/tests.py
@@ -0,0 +1,356 @@
+import datetime
+from django.contrib.syndication import feeds, views
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+from django.utils import tzinfo
+from django.utils.feedgenerator import rfc2822_date, rfc3339_date
+from models import Entry
+from xml.dom import minidom
+
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+class FeedTestCase(TestCase):
+ fixtures = ['feeddata.json']
+
+ def assertChildNodes(self, elem, expected):
+ actual = set([n.nodeName for n in elem.childNodes])
+ expected = set(expected)
+ self.assertEqual(actual, expected)
+
+ def assertChildNodeContent(self, elem, expected):
+ for k, v in expected.items():
+ self.assertEqual(
+ elem.getElementsByTagName(k)[0].firstChild.wholeText, v)
+
+ def assertCategories(self, elem, expected):
+ self.assertEqual(set(i.firstChild.wholeText for i in elem.childNodes if i.nodeName == 'category'), set(expected));
+
+######################################
+# Feed view
+######################################
+
+class SyndicationFeedTest(FeedTestCase):
+ """
+ Tests for the high-level syndication feed framework.
+ """
+
+ def test_rss2_feed(self):
+ """
+ Test the structure and content of feeds generated by Rss201rev2Feed.
+ """
+ response = self.client.get('/syndication/rss2/')
+ doc = minidom.parseString(response.content)
+
+ # Making sure there's only 1 `rss` element and that the correct
+ # RSS version was specified.
+ feed_elem = doc.getElementsByTagName('rss')
+ self.assertEqual(len(feed_elem), 1)
+ feed = feed_elem[0]
+ self.assertEqual(feed.getAttribute('version'), '2.0')
+
+ # Making sure there's only one `channel` element w/in the
+ # `rss` element.
+ chan_elem = feed.getElementsByTagName('channel')
+ self.assertEqual(len(chan_elem), 1)
+ chan = chan_elem[0]
+
+ # Find the last build date
+ d = Entry.objects.latest('date').date
+ ltz = tzinfo.LocalTimezone(d)
+ last_build_date = rfc2822_date(d.replace(tzinfo=ltz))
+
+ self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link', 'ttl', 'copyright', 'category'])
+ self.assertChildNodeContent(chan, {
+ 'title': 'My blog',
+ 'description': 'A more thorough description of my blog.',
+ 'link': 'http://example.com/blog/',
+ 'language': 'en',
+ 'lastBuildDate': last_build_date,
+ #'atom:link': '',
+ 'ttl': '600',
+ 'copyright': 'Copyright (c) 2007, Sally Smith',
+ })
+ self.assertCategories(chan, ['python', 'django']);
+
+ # Ensure the content of the channel is correct
+ self.assertChildNodeContent(chan, {
+ 'title': 'My blog',
+ 'link': 'http://example.com/blog/',
+ })
+
+ # Check feed_url is passed
+ self.assertEqual(
+ chan.getElementsByTagName('atom:link')[0].getAttribute('href'),
+ 'http://example.com/syndication/rss2/'
+ )
+
+ # Find the pubdate of the first feed item
+ d = Entry.objects.get(pk=1).date
+ ltz = tzinfo.LocalTimezone(d)
+ pub_date = rfc2822_date(d.replace(tzinfo=ltz))
+
+ items = chan.getElementsByTagName('item')
+ self.assertEqual(len(items), Entry.objects.count())
+ self.assertChildNodeContent(items[0], {
+ 'title': 'My first entry',
+ 'description': 'Overridden description: My first entry',
+ 'link': 'http://example.com/blog/1/',
+ 'guid': 'http://example.com/blog/1/',
+ 'pubDate': pub_date,
+ 'author': 'test@example.com (Sally Smith)',
+ })
+ self.assertCategories(items[0], ['python', 'testing']);
+
+ for item in items:
+ self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'category', 'pubDate', 'author'])
+
+ def test_rss091_feed(self):
+ """
+ Test the structure and content of feeds generated by RssUserland091Feed.
+ """
+ response = self.client.get('/syndication/rss091/')
+ doc = minidom.parseString(response.content)
+
+ # Making sure there's only 1 `rss` element and that the correct
+ # RSS version was specified.
+ feed_elem = doc.getElementsByTagName('rss')
+ self.assertEqual(len(feed_elem), 1)
+ feed = feed_elem[0]
+ self.assertEqual(feed.getAttribute('version'), '0.91')
+
+ # Making sure there's only one `channel` element w/in the
+ # `rss` element.
+ chan_elem = feed.getElementsByTagName('channel')
+ self.assertEqual(len(chan_elem), 1)
+ chan = chan_elem[0]
+ self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link', 'ttl', 'copyright', 'category'])
+
+ # Ensure the content of the channel is correct
+ self.assertChildNodeContent(chan, {
+ 'title': 'My blog',
+ 'link': 'http://example.com/blog/',
+ })
+ self.assertCategories(chan, ['python', 'django'])
+
+ # Check feed_url is passed
+ self.assertEqual(
+ chan.getElementsByTagName('atom:link')[0].getAttribute('href'),
+ 'http://example.com/syndication/rss091/'
+ )
+
+ items = chan.getElementsByTagName('item')
+ self.assertEqual(len(items), Entry.objects.count())
+ self.assertChildNodeContent(items[0], {
+ 'title': 'My first entry',
+ 'description': 'Overridden description: My first entry',
+ 'link': 'http://example.com/blog/1/',
+ })
+ for item in items:
+ self.assertChildNodes(item, ['title', 'link', 'description'])
+ self.assertCategories(item, [])
+
+ def test_atom_feed(self):
+ """
+ Test the structure and content of feeds generated by Atom1Feed.
+ """
+ response = self.client.get('/syndication/atom/')
+ feed = minidom.parseString(response.content).firstChild
+
+ self.assertEqual(feed.nodeName, 'feed')
+ self.assertEqual(feed.getAttribute('xmlns'), 'http://www.w3.org/2005/Atom')
+ self.assertChildNodes(feed, ['title', 'subtitle', 'link', 'id', 'updated', 'entry', 'rights', 'category', 'author'])
+ for link in feed.getElementsByTagName('link'):
+ if link.getAttribute('rel') == 'self':
+ self.assertEqual(link.getAttribute('href'), 'http://example.com/syndication/atom/')
+
+ entries = feed.getElementsByTagName('entry')
+ self.assertEqual(len(entries), Entry.objects.count())
+ for entry in entries:
+ self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'category', 'updated', 'rights', 'author'])
+ summary = entry.getElementsByTagName('summary')[0]
+ self.assertEqual(summary.getAttribute('type'), 'html')
+
+ def test_custom_feed_generator(self):
+ response = self.client.get('/syndication/custom/')
+ feed = minidom.parseString(response.content).firstChild
+
+ self.assertEqual(feed.nodeName, 'feed')
+ self.assertEqual(feed.getAttribute('django'), 'rocks')
+ self.assertChildNodes(feed, ['title', 'subtitle', 'link', 'id', 'updated', 'entry', 'spam', 'rights', 'category', 'author'])
+
+ entries = feed.getElementsByTagName('entry')
+ self.assertEqual(len(entries), Entry.objects.count())
+ for entry in entries:
+ self.assertEqual(entry.getAttribute('bacon'), 'yum')
+ self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry', 'rights', 'author', 'updated', 'category'])
+ summary = entry.getElementsByTagName('summary')[0]
+ self.assertEqual(summary.getAttribute('type'), 'html')
+
+ def test_title_escaping(self):
+ """
+ Tests that titles are escaped correctly in RSS feeds.
+ """
+ response = self.client.get('/syndication/rss2/')
+ doc = minidom.parseString(response.content)
+ for item in doc.getElementsByTagName('item'):
+ link = item.getElementsByTagName('link')[0]
+ if link.firstChild.wholeText == 'http://example.com/blog/4/':
+ title = item.getElementsByTagName('title')[0]
+ self.assertEquals(title.firstChild.wholeText, u'A &amp; B &lt; C &gt; D')
+
+ def test_naive_datetime_conversion(self):
+ """
+ Test that datetimes are correctly converted to the local time zone.
+ """
+ # Naive date times passed in get converted to the local time zone, so
+ # check the recived zone offset against the local offset.
+ response = self.client.get('/syndication/naive-dates/')
+ doc = minidom.parseString(response.content)
+ updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
+
+ d = Entry.objects.latest('date').date
+ ltz = tzinfo.LocalTimezone(d)
+ latest = rfc3339_date(d.replace(tzinfo=ltz))
+
+ self.assertEqual(updated, latest)
+
+ def test_aware_datetime_conversion(self):
+ """
+ Test that datetimes with timezones don't get trodden on.
+ """
+ response = self.client.get('/syndication/aware-dates/')
+ doc = minidom.parseString(response.content)
+ updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
+ self.assertEqual(updated[-6:], '+00:42')
+
+ def test_feed_url(self):
+ """
+ Test that the feed_url can be overridden.
+ """
+ response = self.client.get('/syndication/feedurl/')
+ doc = minidom.parseString(response.content)
+ for link in doc.getElementsByTagName('link'):
+ if link.getAttribute('rel') == 'self':
+ self.assertEqual(link.getAttribute('href'), 'http://example.com/customfeedurl/')
+
+ def test_secure_urls(self):
+ """
+ Test URLs are prefixed with https:// when feed is requested over HTTPS.
+ """
+ response = self.client.get('/syndication/rss2/', **{
+ 'wsgi.url_scheme': 'https',
+ })
+ doc = minidom.parseString(response.content)
+ chan = doc.getElementsByTagName('channel')[0]
+ self.assertEqual(
+ chan.getElementsByTagName('link')[0].firstChild.wholeText[0:5],
+ 'https'
+ )
+ atom_link = chan.getElementsByTagName('atom:link')[0]
+ self.assertEqual(atom_link.getAttribute('href')[0:5], 'https')
+ for link in doc.getElementsByTagName('link'):
+ if link.getAttribute('rel') == 'self':
+ self.assertEqual(link.getAttribute('href')[0:5], 'https')
+
+ def test_item_link_error(self):
+ """
+ Test that a ImproperlyConfigured is raised if no link could be found
+ for the item(s).
+ """
+ self.assertRaises(ImproperlyConfigured,
+ self.client.get,
+ '/syndication/articles/')
+
+ def test_template_feed(self):
+ """
+ Test that the item title and description can be overridden with
+ templates.
+ """
+ response = self.client.get('/syndication/template/')
+ doc = minidom.parseString(response.content)
+ feed = doc.getElementsByTagName('rss')[0]
+ chan = feed.getElementsByTagName('channel')[0]
+ items = chan.getElementsByTagName('item')
+
+ self.assertChildNodeContent(items[0], {
+ 'title': 'Title in your templates: My first entry',
+ 'description': 'Description in your templates: My first entry',
+ 'link': 'http://example.com/blog/1/',
+ })
+
+ def test_add_domain(self):
+ """
+ Test add_domain() prefixes domains onto the correct URLs.
+ """
+ self.assertEqual(
+ views.add_domain('example.com', '/foo/?arg=value'),
+ 'http://example.com/foo/?arg=value'
+ )
+ self.assertEqual(
+ views.add_domain('example.com', '/foo/?arg=value', True),
+ 'https://example.com/foo/?arg=value'
+ )
+ self.assertEqual(
+ views.add_domain('example.com', 'http://djangoproject.com/doc/'),
+ 'http://djangoproject.com/doc/'
+ )
+ self.assertEqual(
+ views.add_domain('example.com', 'https://djangoproject.com/doc/'),
+ 'https://djangoproject.com/doc/'
+ )
+ self.assertEqual(
+ views.add_domain('example.com', 'mailto:uhoh@djangoproject.com'),
+ 'mailto:uhoh@djangoproject.com'
+ )
+
+
+######################################
+# Deprecated feeds
+######################################
+
+class DeprecatedSyndicationFeedTest(FeedTestCase):
+ """
+ Tests for the deprecated API (feed() view and the feed_dict etc).
+ """
+
+ def test_empty_feed_dict(self):
+ """
+ Test that an empty feed_dict raises a 404.
+ """
+ response = self.client.get('/syndication/depr-feeds-empty/aware-dates/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_nonexistent_slug(self):
+ """
+ Test that a non-existent slug raises a 404.
+ """
+ response = self.client.get('/syndication/depr-feeds/foobar/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_rss_feed(self):
+ """
+ A simple test for Rss201rev2Feed feeds generated by the deprecated
+ system.
+ """
+ response = self.client.get('/syndication/depr-feeds/rss/')
+ doc = minidom.parseString(response.content)
+ feed = doc.getElementsByTagName('rss')[0]
+ self.assertEqual(feed.getAttribute('version'), '2.0')
+
+ chan = feed.getElementsByTagName('channel')[0]
+ self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link'])
+
+ items = chan.getElementsByTagName('item')
+ self.assertEqual(len(items), Entry.objects.count())
+
+ def test_complex_base_url(self):
+ """
+ Tests that the base url for a complex feed doesn't raise a 500
+ exception.
+ """
+ response = self.client.get('/syndication/depr-feeds/complex/')
+ self.assertEquals(response.status_code, 404)
+
diff --git a/parts/django/tests/regressiontests/syndication/urls.py b/parts/django/tests/regressiontests/syndication/urls.py
new file mode 100644
index 0000000..881fa48
--- /dev/null
+++ b/parts/django/tests/regressiontests/syndication/urls.py
@@ -0,0 +1,24 @@
+from django.conf.urls.defaults import *
+
+import feeds
+
+feed_dict = {
+ 'complex': feeds.DeprecatedComplexFeed,
+ 'rss': feeds.DeprecatedRssFeed,
+}
+
+urlpatterns = patterns('django.contrib.syndication.views',
+ (r'^complex/(?P<foo>.*)/$', feeds.ComplexFeed()),
+ (r'^rss2/$', feeds.TestRss2Feed()),
+ (r'^rss091/$', feeds.TestRss091Feed()),
+ (r'^atom/$', feeds.TestAtomFeed()),
+ (r'^custom/$', feeds.TestCustomFeed()),
+ (r'^naive-dates/$', feeds.NaiveDatesFeed()),
+ (r'^aware-dates/$', feeds.TZAwareDatesFeed()),
+ (r'^feedurl/$', feeds.TestFeedUrlFeed()),
+ (r'^articles/$', feeds.ArticlesFeed()),
+ (r'^template/$', feeds.TemplateFeed()),
+
+ (r'^depr-feeds/(?P<url>.*)/$', 'feed', {'feed_dict': feed_dict}),
+ (r'^depr-feeds-empty/(?P<url>.*)/$', 'feed', {'feed_dict': None}),
+)
diff --git a/parts/django/tests/regressiontests/templates/__init__.py b/parts/django/tests/regressiontests/templates/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/__init__.py
diff --git a/parts/django/tests/regressiontests/templates/context.py b/parts/django/tests/regressiontests/templates/context.py
new file mode 100644
index 0000000..394de94
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/context.py
@@ -0,0 +1,17 @@
+# coding: utf-8
+from unittest import TestCase
+
+from django.template import Context
+
+
+class ContextTests(TestCase):
+ def test_context(self):
+ c = Context({"a": 1, "b": "xyzzy"})
+ self.assertEqual(c["a"], 1)
+ self.assertEqual(c.push(), {})
+ c["a"] = 2
+ self.assertEqual(c["a"], 2)
+ self.assertEqual(c.get("a"), 2)
+ self.assertEqual(c.pop(), {"a": 2})
+ self.assertEqual(c["a"], 1)
+ self.assertEqual(c.get("foo", 42), 42)
diff --git a/parts/django/tests/regressiontests/templates/custom.py b/parts/django/tests/regressiontests/templates/custom.py
new file mode 100644
index 0000000..b346198
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/custom.py
@@ -0,0 +1,12 @@
+from unittest import TestCase
+
+from django import template
+
+
+class CustomTests(TestCase):
+ def test_filter(self):
+ t = template.Template("{% load custom %}{{ string|trim:5 }}")
+ self.assertEqual(
+ t.render(template.Context({"string": "abcdefghijklmnopqrstuvwxyz"})),
+ u"abcde"
+ )
diff --git a/parts/django/tests/regressiontests/templates/eggs/tagsegg.egg b/parts/django/tests/regressiontests/templates/eggs/tagsegg.egg
new file mode 100755
index 0000000..3941914
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/eggs/tagsegg.egg
Binary files differ
diff --git a/parts/django/tests/regressiontests/templates/filters.py b/parts/django/tests/regressiontests/templates/filters.py
new file mode 100644
index 0000000..af34c58
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/filters.py
@@ -0,0 +1,350 @@
+# coding: utf-8
+"""
+Tests for template filters (as opposed to template tags).
+
+The tests are hidden inside a function so that things like timestamps and
+timezones are only evaluated at the moment of execution and will therefore be
+consistent.
+"""
+
+from datetime import date, datetime, timedelta
+
+from django.utils.tzinfo import LocalTimezone, FixedOffset
+from django.utils.safestring import mark_safe
+
+# These two classes are used to test auto-escaping of __unicode__ output.
+class UnsafeClass:
+ def __unicode__(self):
+ return u'you & me'
+
+class SafeClass:
+ def __unicode__(self):
+ return mark_safe(u'you &gt; me')
+
+# RESULT SYNTAX --
+# 'template_name': ('template contents', 'context dict',
+# 'expected string output' or Exception class)
+def get_filter_tests():
+ now = datetime.now()
+ now_tz = datetime.now(LocalTimezone(now))
+ now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone
+ today = date.today()
+
+ return {
+ # Default compare with datetime.now()
+ 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
+ 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'),
+ 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'),
+
+ # Compare to a given parameter
+ 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1 day'),
+ 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1 minute'),
+
+ # Check that timezone is respected
+ 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8 hours'),
+
+ # Regression for #7443
+ 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1 week'),
+ 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1 week'),
+ 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'),
+ 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'),
+
+ # Ensures that differing timezones are calculated correctly
+ 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'),
+ 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'),
+ 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'),
+ 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'),
+ 'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''),
+ 'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''),
+
+ # Regression for #9065 (two date objects).
+ 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0 minutes'),
+ 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1 day'),
+
+ # Default compare with datetime.now()
+ 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
+ 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
+ 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
+
+ # Compare to a given parameter
+ 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'),
+ 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'),
+
+ # Regression for #7443
+ 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0 minutes'),
+ 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0 minutes'),
+ 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'),
+ 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'),
+
+ # Ensures that differing timezones are calculated correctly
+ 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'),
+ 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'),
+
+ # Regression for #9065 (two date objects).
+ 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0 minutes'),
+ 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1 day'),
+
+ 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"),
+ 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"&lt;a&gt;\&#39; <a>\'"),
+
+ 'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred&gt;")}, u"Fred> Fred&gt;"),
+ 'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred&gt;")}, u"Fred&gt; Fred&gt;"),
+
+ # Note that applying fix_ampsersands in autoescape mode leads to
+ # double escaping.
+ 'filter-fix_ampersands01': ("{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&amp;b a&amp;b"),
+ 'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&amp;amp;b a&amp;b"),
+
+ 'filter-floatformat01': ("{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"),
+ 'filter-floatformat02': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"),
+
+ # The contents of "linenumbers" is escaped according to the current
+ # autoescape setting.
+ 'filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n&lt;two&gt;\nthree")}, u"1. one\n2. &lt;two&gt;\n3. three 1. one\n2. &lt;two&gt;\n3. three"),
+ 'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n&lt;two&gt;\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. &lt;two&gt;\n3. three"),
+
+ 'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple &amp; banana")}, u"apple & banana apple &amp; banana"),
+ 'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple &amp; banana")}, u"apple &amp; banana apple &amp; banana"),
+
+ # The make_list filter can destroy existing escaping, so the results are
+ # escaped.
+ 'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, u"[u'&']"),
+ 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, u"[u&#39;&amp;&#39;]"),
+ 'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, u"[u'&']"),
+ 'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, u"[u'&']"),
+
+ # Running slugify on a pre-escaped string leads to odd behaviour,
+ # but the result is still safe.
+ 'filter-slugify01': ("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a &amp; b")}, u"a-b a-amp-b"),
+ 'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a &amp; b")}, u"a-b a-amp-b"),
+
+ # Notice that escaping is applied *after* any filters, so the string
+ # formatting here only needs to deal with pre-escaped characters.
+ 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}',
+ {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."),
+ 'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")},
+ u". a&lt;b. . a<b."),
+
+ # Test the title filter
+ 'filter-title1' : ('{{ a|title }}', {'a' : 'JOE\'S CRAB SHACK'}, u'Joe&#39;s Crab Shack'),
+ 'filter-title2' : ('{{ a|title }}', {'a' : '555 WEST 53RD STREET'}, u'555 West 53rd Street'),
+
+ 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}',
+ {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha & ... alpha &amp; ..."),
+ 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}',
+ {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha &amp; ... alpha &amp; ..."),
+
+ # The "upper" filter messes up entities (which are case-sensitive),
+ # so it's not safe for non-escaping purposes.
+ 'filter-upper01': ('{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a &amp; b")}, u"A & B A &AMP; B"),
+ 'filter-upper02': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, u"A &amp; B A &amp;AMP; B"),
+
+ 'filter-urlize01': ('{% autoescape off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}', {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&amp;y=")}, u'<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&amp;y=" rel="nofollow">http://example.com?x=&amp;y=</a>'),
+ 'filter-urlize02': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&amp;y=")}, u'<a href="http://example.com/?x=&amp;y=" rel="nofollow">http://example.com/?x=&amp;y=</a> <a href="http://example.com?x=&amp;y=" rel="nofollow">http://example.com?x=&amp;y=</a>'),
+ 'filter-urlize03': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": mark_safe("a &amp; b")}, 'a &amp; b'),
+ 'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a &amp; b")}, 'a &amp; b'),
+
+ # This will lead to a nonsense result, but at least it won't be
+ # exploitable for XSS purposes when auto-escaping is on.
+ 'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>"),
+ 'filter-urlize06': ('{{ a|urlize }}', {"a": "<script>alert('foo')</script>"}, '&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;'),
+
+ # mailto: testing for urlize
+ 'filter-urlize07': ('{{ a|urlize }}', {"a": "Email me at me@example.com"}, 'Email me at <a href="mailto:me@example.com">me@example.com</a>'),
+ 'filter-urlize08': ('{{ a|urlize }}', {"a": "Email me at <me@example.com>"}, 'Email me at &lt;<a href="mailto:me@example.com">me@example.com</a>&gt;'),
+
+ 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('&quot;Safe&quot; http://example.com?x=&amp;y=')}, u'"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> &quot;Safe&quot; <a href="http://example.com?x=&amp;y=" rel="nofollow">http:...</a>'),
+ 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('&quot;Safe&quot; http://example.com?x=&amp;y=')}, u'&quot;Unsafe&quot; <a href="http://example.com/x=&amp;y=" rel="nofollow">http:...</a> &quot;Safe&quot; <a href="http://example.com?x=&amp;y=" rel="nofollow">http:...</a>'),
+
+ 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),
+ 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),
+
+ 'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"),
+ 'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &amp;\nb a &\nb"),
+
+ 'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."),
+ 'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u".a&amp;b . .a&b ."),
+
+ 'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."),
+ 'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&amp;b. . a&b."),
+
+ 'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."),
+ 'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&amp;b . . a&b ."),
+
+ 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"&y &amp;y"),
+ 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"&amp;y &amp;y"),
+ 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"xy xamp;y"),
+ 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"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&amp;y")}, u"x&y x&ampy"),
+ 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"x&amp;y x&amp;ampy"),
+
+ # 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")}, u"x&amp;y x&y"),
+ 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;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"}, u"x&amp;y"),
+ 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, u"x&amp;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"}, u"x&amp;y"),
+ 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, u"x&amp;y"),
+ 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;amp;y"),
+ 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, u"x&amp;amp;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"}, u"x&amp;y"),
+ 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&amp;y"),
+ 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"),
+ 'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&amp;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")}, u"<p>x&amp;<br />y</p> <p>x&<br />y</p>"),
+ 'filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"<p>x&<br />y</p> <p>x&<br />y</p>"),
+
+ 'filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&amp;<br />y x&<br />y"),
+ 'filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&<br />y x&<br />y"),
+
+ 'filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": u"<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>"),
+ 'filter-safe02': ("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "<b>hello</b>"}, u"<b>hello</b> -- <b>hello</b>"),
+
+ 'filter-safeseq01': ('{{ a|join:", " }} -- {{ a|safeseq|join:", " }}', {"a": ["&", "<"]}, "&amp;, &lt; -- &, <"),
+ 'filter-safeseq02': ('{% autoescape off %}{{ a|join:", " }} -- {{ a|safeseq|join:", " }}{% endautoescape %}', {"a": ["&", "<"]}, "&, < -- &, <"),
+
+ 'filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, u"x &lt;p&gt;y&lt;/p&gt; x <p>y</p>"),
+ 'filter-removetags02': ('{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, u"x <p>y</p> x <p>y</p>"),
+
+ 'filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"),
+ 'filter-striptags02': ('{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"),
+
+ 'filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&amp;b a&b"),
+ 'filter-first02': ('{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"),
+
+ 'filter-last01': ('{{ a|last }} {{ b|last }}', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&amp;b a&b"),
+ 'filter-last02': ('{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b"),
+
+ 'filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&amp;b a&b"),
+ 'filter-random02': ('{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"),
+
+ 'filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&amp;b &b"),
+ 'filter-slice02': ('{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"),
+
+ 'filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x&gt;\n\t<ul>\n\t\t<li>&lt;y</li>\n\t</ul>\n\t</li>"),
+ 'filter-unordered_list02': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
+ 'filter-unordered_list03': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x&gt;\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
+ 'filter-unordered_list04': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
+ 'filter-unordered_list05': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
+
+ # Literal string arguments to the default filter are always treated as
+ # safe strings, regardless of the auto-escaping state.
+ #
+ # Note: we have to use {"a": ""} here, otherwise the invalid template
+ # variable string interferes with the test result.
+ 'filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"),
+ 'filter-default02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"),
+ 'filter-default03': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"),
+ 'filter-default04': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>"),
+
+ 'filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"),
+ 'filter-default_if_none02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"),
+
+ 'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "&lt;1-800-2255-63&gt; <1-800-2255-63>"),
+ 'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"),
+ 'filter-phone2numeric03': ('{{ a|phone2numeric }}', {"a": "How razorback-jumping frogs can level six piqued gymnasts!"}, "469 729672225-5867464 37647 226 53835 749 747833 49662787!"),
+
+ # Ensure iriencode keeps safe strings:
+ 'filter-iriencode01': ('{{ url|iriencode }}', {'url': '?test=1&me=2'}, '?test=1&amp;me=2'),
+ 'filter-iriencode02': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': '?test=1&me=2'}, '?test=1&me=2'),
+ 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'),
+ 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'),
+
+ # Chaining a bunch of safeness-preserving filters should not alter
+ # the safe status either way.
+ 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A &lt; b . A < b "),
+ 'chaining02': ('{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "),
+
+ # Using a filter that forces a string back to unsafe:
+ 'chaining03': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A &lt; .A < "),
+ 'chaining04': ('{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "),
+
+ # Using a filter that forces safeness does not lead to double-escaping
+ 'chaining05': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A &lt; b"),
+ 'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A &lt; b"),
+
+ # Force to safe, then back (also showing why using force_escape too
+ # early in a chain can lead to unexpected results).
+ 'chaining07': ('{{ a|force_escape|cut:";" }}', {"a": "a < b"}, "a &amp;lt b"),
+ 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:";" }}{% endautoescape %}', {"a": "a < b"}, "a &lt b"),
+ 'chaining09': ('{{ a|cut:";"|force_escape }}', {"a": "a < b"}, "a &lt; b"),
+ 'chaining10': ('{% autoescape off %}{{ a|cut:";"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a &lt; b"),
+ 'chaining11': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "),
+ 'chaining12': ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "),
+ 'chaining13': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a &lt; b"),
+ 'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a &lt; b"),
+
+ # Filters decorated with stringfilter still respect is_safe.
+ 'autoescape-stringfilter01': (r'{{ unsafe|capfirst }}', {'unsafe': UnsafeClass()}, 'You &amp; me'),
+ 'autoescape-stringfilter02': (r'{% autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}', {'unsafe': UnsafeClass()}, 'You & me'),
+ 'autoescape-stringfilter03': (r'{{ safe|capfirst }}', {'safe': SafeClass()}, 'You &gt; me'),
+ 'autoescape-stringfilter04': (r'{% autoescape off %}{{ safe|capfirst }}{% endautoescape %}', {'safe': SafeClass()}, 'You &gt; me'),
+
+ 'escapejs01': (r'{{ a|escapejs }}', {'a': 'testing\r\njavascript \'string" <b>escaping</b>'}, 'testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E'),
+ 'escapejs02': (r'{% autoescape off %}{{ a|escapejs }}{% endautoescape %}', {'a': 'testing\r\njavascript \'string" <b>escaping</b>'}, 'testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E'),
+
+
+ # length filter.
+ 'length01': ('{{ list|length }}', {'list': ['4', None, True, {}]}, '4'),
+ 'length02': ('{{ list|length }}', {'list': []}, '0'),
+ 'length03': ('{{ string|length }}', {'string': ''}, '0'),
+ 'length04': ('{{ string|length }}', {'string': 'django'}, '6'),
+ # Invalid uses that should fail silently.
+ 'length05': ('{{ int|length }}', {'int': 7}, ''),
+ 'length06': ('{{ None|length }}', {'None': None}, ''),
+
+ # length_is filter.
+ 'length_is01': ('{% if some_list|length_is:"4" %}Four{% endif %}', {'some_list': ['4', None, True, {}]}, 'Four'),
+ 'length_is02': ('{% if some_list|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'some_list': ['4', None, True, {}, 17]}, 'Not Four'),
+ 'length_is03': ('{% if mystring|length_is:"4" %}Four{% endif %}', {'mystring': 'word'}, 'Four'),
+ 'length_is04': ('{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'mystring': 'Python'}, 'Not Four'),
+ 'length_is05': ('{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'mystring': ''}, 'Not Four'),
+ 'length_is06': ('{% with var|length as my_length %}{{ my_length }}{% endwith %}', {'var': 'django'}, '6'),
+ # Boolean return value from length_is should not be coerced to a string
+ 'length_is07': (r'{% if "X"|length_is:0 %}Length is 0{% else %}Length not 0{% endif %}', {}, 'Length not 0'),
+ 'length_is08': (r'{% if "X"|length_is:1 %}Length is 1{% else %}Length not 1{% endif %}', {}, 'Length is 1'),
+ # Invalid uses that should fail silently.
+ 'length_is09': ('{{ var|length_is:"fish" }}', {'var': 'django'}, ''),
+ 'length_is10': ('{{ int|length_is:"1" }}', {'int': 7}, ''),
+ 'length_is11': ('{{ none|length_is:"1" }}', {'none': None}, ''),
+
+ 'join01': (r'{{ a|join:", " }}', {'a': ['alpha', 'beta & me']}, 'alpha, beta &amp; me'),
+ 'join02': (r'{% autoescape off %}{{ a|join:", " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha, beta & me'),
+ 'join03': (r'{{ a|join:" &amp; " }}', {'a': ['alpha', 'beta & me']}, 'alpha &amp; beta &amp; me'),
+ 'join04': (r'{% autoescape off %}{{ a|join:" &amp; " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha &amp; beta & me'),
+
+ # Test that joining with unsafe joiners don't result in unsafe strings (#11377)
+ 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha &amp; beta &amp; me'),
+ 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta &amp; me'),
+ 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha &amp; beta &amp; me'),
+ 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta &amp; me'),
+
+ 'date01': (r'{{ d|date:"m" }}', {'d': datetime(2008, 1, 1)}, '01'),
+ 'date02': (r'{{ d|date }}', {'d': datetime(2008, 1, 1)}, 'Jan. 1, 2008'),
+ #Ticket 9520: Make sure |date doesn't blow up on non-dates
+ 'date03': (r'{{ d|date:"m" }}', {'d': 'fail_string'}, ''),
+
+ # Tests for #11687
+ 'add01': (r'{{ i|add:"5" }}', {'i': 2000}, '2005'),
+ 'add02': (r'{{ i|add:"napis" }}', {'i': 2000}, '2000'),
+ 'add03': (r'{{ i|add:16 }}', {'i': 'not_an_int'}, 'not_an_int'),
+ 'add04': (r'{{ i|add:"16" }}', {'i': 'not_an_int'}, 'not_an_int16'),
+ 'add05': (r'{{ l1|add:l2 }}', {'l1': [1, 2], 'l2': [3, 4]}, '[1, 2, 3, 4]'),
+ 'add06': (r'{{ t1|add:t2 }}', {'t1': (3, 4), 't2': (1, 2)}, '(3, 4, 1, 2)'),
+ 'add07': (r'{{ d|add:t }}', {'d': date(2000, 1, 1), 't': timedelta(10)}, 'Jan. 11, 2000'),
+ }
diff --git a/parts/django/tests/regressiontests/templates/loaders.py b/parts/django/tests/regressiontests/templates/loaders.py
new file mode 100644
index 0000000..47cd18a
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/loaders.py
@@ -0,0 +1,150 @@
+"""
+Test cases for the template loaders
+
+Note: This test requires setuptools!
+"""
+
+from django.conf import settings
+
+if __name__ == '__main__':
+ settings.configure()
+
+import unittest
+import sys
+import pkg_resources
+import imp
+import StringIO
+import os.path
+import warnings
+
+from django.template import TemplateDoesNotExist, Context
+from django.template.loaders.eggs import load_template_source as lts_egg
+from django.template.loaders.eggs import Loader as EggLoader
+from django.template import loader
+from django.test.utils import get_warnings_state, restore_warnings_state
+
+# Mock classes and objects for pkg_resources functions.
+class MockProvider(pkg_resources.NullProvider):
+ def __init__(self, module):
+ pkg_resources.NullProvider.__init__(self, module)
+ self.module = module
+
+ def _has(self, path):
+ return path in self.module._resources
+
+ def _isdir(self,path):
+ return False
+
+ def get_resource_stream(self, manager, resource_name):
+ return self.module._resources[resource_name]
+
+ def _get(self, path):
+ return self.module._resources[path].read()
+
+class MockLoader(object):
+ pass
+
+def create_egg(name, resources):
+ """
+ Creates a mock egg with a list of resources.
+
+ name: The name of the module.
+ resources: A dictionary of resources. Keys are the names and values the data.
+ """
+ egg = imp.new_module(name)
+ egg.__loader__ = MockLoader()
+ egg._resources = resources
+ sys.modules[name] = egg
+
+class DeprecatedEggLoaderTest(unittest.TestCase):
+ "Test the deprecated load_template_source interface to the egg loader"
+ def setUp(self):
+ pkg_resources._provider_factories[MockLoader] = MockProvider
+
+ self.empty_egg = create_egg("egg_empty", {})
+ self.egg_1 = create_egg("egg_1", {
+ os.path.normcase('templates/y.html') : StringIO.StringIO("y"),
+ os.path.normcase('templates/x.txt') : StringIO.StringIO("x"),
+ })
+ self._old_installed_apps = settings.INSTALLED_APPS
+ settings.INSTALLED_APPS = []
+ self._warnings_state = get_warnings_state()
+ warnings.simplefilter("ignore", PendingDeprecationWarning)
+
+ def tearDown(self):
+ settings.INSTALLED_APPS = self._old_installed_apps
+ restore_warnings_state(self._warnings_state)
+
+ def test_existing(self):
+ "A template can be loaded from an egg"
+ settings.INSTALLED_APPS = ['egg_1']
+ contents, template_name = lts_egg("y.html")
+ self.assertEqual(contents, "y")
+ self.assertEqual(template_name, "egg:egg_1:templates/y.html")
+
+
+class EggLoaderTest(unittest.TestCase):
+ def setUp(self):
+ pkg_resources._provider_factories[MockLoader] = MockProvider
+
+ self.empty_egg = create_egg("egg_empty", {})
+ self.egg_1 = create_egg("egg_1", {
+ os.path.normcase('templates/y.html') : StringIO.StringIO("y"),
+ os.path.normcase('templates/x.txt') : StringIO.StringIO("x"),
+ })
+ self._old_installed_apps = settings.INSTALLED_APPS
+ settings.INSTALLED_APPS = []
+
+ def tearDown(self):
+ settings.INSTALLED_APPS = self._old_installed_apps
+
+ def test_empty(self):
+ "Loading any template on an empty egg should fail"
+ settings.INSTALLED_APPS = ['egg_empty']
+ egg_loader = EggLoader()
+ self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html")
+
+ def test_non_existing(self):
+ "Template loading fails if the template is not in the egg"
+ settings.INSTALLED_APPS = ['egg_1']
+ egg_loader = EggLoader()
+ self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html")
+
+ def test_existing(self):
+ "A template can be loaded from an egg"
+ settings.INSTALLED_APPS = ['egg_1']
+ egg_loader = EggLoader()
+ contents, template_name = egg_loader.load_template_source("y.html")
+ self.assertEqual(contents, "y")
+ self.assertEqual(template_name, "egg:egg_1:templates/y.html")
+
+ def test_not_installed(self):
+ "Loading an existent template from an egg not included in INSTALLED_APPS should fail"
+ settings.INSTALLED_APPS = []
+ egg_loader = EggLoader()
+ self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "y.html")
+
+class CachedLoader(unittest.TestCase):
+ def setUp(self):
+ self.old_TEMPLATE_LOADERS = settings.TEMPLATE_LOADERS
+ settings.TEMPLATE_LOADERS = (
+ ('django.template.loaders.cached.Loader', (
+ 'django.template.loaders.filesystem.Loader',
+ )
+ ),
+ )
+ def tearDown(self):
+ settings.TEMPLATE_LOADERS = self.old_TEMPLATE_LOADERS
+
+ def test_templatedir_caching(self):
+ "Check that the template directories form part of the template cache key. Refs #13573"
+ # Retrive a template specifying a template directory to check
+ t1, name = loader.find_template('test.html', (os.path.join(os.path.dirname(__file__), 'templates', 'first'),))
+ # Now retrieve the same template name, but from a different directory
+ t2, name = loader.find_template('test.html', (os.path.join(os.path.dirname(__file__), 'templates', 'second'),))
+
+ # The two templates should not have the same content
+ self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/parts/django/tests/regressiontests/templates/models.py b/parts/django/tests/regressiontests/templates/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/models.py
diff --git a/parts/django/tests/regressiontests/templates/nodelist.py b/parts/django/tests/regressiontests/templates/nodelist.py
new file mode 100644
index 0000000..89fac97
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/nodelist.py
@@ -0,0 +1,30 @@
+from unittest import TestCase
+from django.template.loader import get_template_from_string
+from django.template import VariableNode
+
+
+class NodelistTest(TestCase):
+
+ def test_for(self):
+ source = '{% for i in 1 %}{{ a }}{% endfor %}'
+ template = get_template_from_string(source)
+ vars = template.nodelist.get_nodes_by_type(VariableNode)
+ self.assertEqual(len(vars), 1)
+
+ def test_if(self):
+ source = '{% if x %}{{ a }}{% endif %}'
+ template = get_template_from_string(source)
+ vars = template.nodelist.get_nodes_by_type(VariableNode)
+ self.assertEqual(len(vars), 1)
+
+ def test_ifequal(self):
+ source = '{% ifequal x y %}{{ a }}{% endifequal %}'
+ template = get_template_from_string(source)
+ vars = template.nodelist.get_nodes_by_type(VariableNode)
+ self.assertEqual(len(vars), 1)
+
+ def test_ifchanged(self):
+ source = '{% ifchanged x %}{{ a }}{% endifchanged %}'
+ template = get_template_from_string(source)
+ vars = template.nodelist.get_nodes_by_type(VariableNode)
+ self.assertEqual(len(vars), 1)
diff --git a/parts/django/tests/regressiontests/templates/parser.py b/parts/django/tests/regressiontests/templates/parser.py
new file mode 100644
index 0000000..93e8118
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/parser.py
@@ -0,0 +1,84 @@
+"""
+Testing some internals of the template processing. These are *not* examples to be copied in user code.
+"""
+from unittest import TestCase
+
+from django.template import (TokenParser, FilterExpression, Parser, Variable,
+ TemplateSyntaxError)
+
+
+class ParserTests(TestCase):
+ def test_token_parsing(self):
+ # Tests for TokenParser behavior in the face of quoted strings with
+ # spaces.
+
+ p = TokenParser("tag thevar|filter sometag")
+ self.assertEqual(p.tagname, "tag")
+ self.assertEqual(p.value(), "thevar|filter")
+ self.assertTrue(p.more())
+ self.assertEqual(p.tag(), "sometag")
+ self.assertFalse(p.more())
+
+ p = TokenParser('tag "a value"|filter sometag')
+ self.assertEqual(p.tagname, "tag")
+ self.assertEqual(p.value(), '"a value"|filter')
+ self.assertTrue(p.more())
+ self.assertEqual(p.tag(), "sometag")
+ self.assertFalse(p.more())
+
+ p = TokenParser("tag 'a value'|filter sometag")
+ self.assertEqual(p.tagname, "tag")
+ self.assertEqual(p.value(), "'a value'|filter")
+ self.assertTrue(p.more())
+ self.assertEqual(p.tag(), "sometag")
+ self.assertFalse(p.more())
+
+ def test_filter_parsing(self):
+ c = {"article": {"section": u"News"}}
+ p = Parser("")
+
+ def fe_test(s, val):
+ self.assertEqual(FilterExpression(s, p).resolve(c), val)
+
+ fe_test("article.section", u"News")
+ fe_test("article.section|upper", u"NEWS")
+ fe_test(u'"News"', u"News")
+ fe_test(u"'News'", u"News")
+ fe_test(ur'"Some \"Good\" News"', u'Some "Good" News')
+ fe_test(ur'"Some \"Good\" News"', u'Some "Good" News')
+ fe_test(ur"'Some \'Bad\' News'", u"Some 'Bad' News")
+
+ fe = FilterExpression(ur'"Some \"Good\" News"', p)
+ self.assertEqual(fe.filters, [])
+ self.assertEqual(fe.var, u'Some "Good" News')
+
+ # Filtered variables should reject access of attributes beginning with
+ # underscores.
+ self.assertRaises(TemplateSyntaxError,
+ FilterExpression, "article._hidden|upper", p
+ )
+
+ def test_variable_parsing(self):
+ c = {"article": {"section": u"News"}}
+ self.assertEqual(Variable("article.section").resolve(c), "News")
+ self.assertEqual(Variable(u'"News"').resolve(c), "News")
+ self.assertEqual(Variable(u"'News'").resolve(c), "News")
+
+ # Translated strings are handled correctly.
+ self.assertEqual(Variable("_(article.section)").resolve(c), "News")
+ self.assertEqual(Variable('_("Good News")').resolve(c), "Good News")
+ self.assertEqual(Variable("_('Better News')").resolve(c), "Better News")
+
+ # Escaped quotes work correctly as well.
+ self.assertEqual(
+ Variable(ur'"Some \"Good\" News"').resolve(c), 'Some "Good" News'
+ )
+ self.assertEqual(
+ Variable(ur"'Some \'Better\' News'").resolve(c), "Some 'Better' News"
+ )
+
+ # Variables should reject access of attributes beginning with
+ # underscores.
+ self.assertRaises(TemplateSyntaxError,
+ Variable, "article._hidden"
+ )
diff --git a/parts/django/tests/regressiontests/templates/smartif.py b/parts/django/tests/regressiontests/templates/smartif.py
new file mode 100644
index 0000000..5e5d770
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/smartif.py
@@ -0,0 +1,53 @@
+import unittest
+from django.template.smartif import IfParser, Literal
+
+class SmartIfTests(unittest.TestCase):
+
+ def assertCalcEqual(self, expected, tokens):
+ self.assertEqual(expected, IfParser(tokens).parse().eval({}))
+
+ # We only test things here that are difficult to test elsewhere
+ # Many other tests are found in the main tests for builtin template tags
+ # Test parsing via the printed parse tree
+ def test_not(self):
+ var = IfParser(["not", False]).parse()
+ self.assertEqual("(not (literal False))", repr(var))
+ self.assert_(var.eval({}))
+
+ self.assertFalse(IfParser(["not", True]).parse().eval({}))
+
+ def test_or(self):
+ var = IfParser([True, "or", False]).parse()
+ self.assertEqual("(or (literal True) (literal False))", repr(var))
+ self.assert_(var.eval({}))
+
+ def test_in(self):
+ list_ = [1,2,3]
+ self.assertCalcEqual(True, [1, 'in', list_])
+ self.assertCalcEqual(False, [1, 'in', None])
+ self.assertCalcEqual(False, [None, 'in', list_])
+
+ def test_not_in(self):
+ list_ = [1,2,3]
+ self.assertCalcEqual(False, [1, 'not', 'in', list_])
+ self.assertCalcEqual(True, [4, 'not', 'in', list_])
+ self.assertCalcEqual(False, [1, 'not', 'in', None])
+ self.assertCalcEqual(True, [None, 'not', 'in', list_])
+
+ def test_precedence(self):
+ # (False and False) or True == True <- we want this one, like Python
+ # False and (False or True) == False
+ self.assertCalcEqual(True, [False, 'and', False, 'or', True])
+
+ # True or (False and False) == True <- we want this one, like Python
+ # (True or False) and False == False
+ self.assertCalcEqual(True, [True, 'or', False, 'and', False])
+
+ # (1 or 1) == 2 -> False
+ # 1 or (1 == 2) -> True <- we want this one
+ self.assertCalcEqual(True, [1, 'or', 1, '==', 2])
+
+ self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False])
+
+ self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) (literal 4))",
+ repr(IfParser([1, '==', 2, 'and', 3, 'or', 4]).parse()))
diff --git a/parts/django/tests/regressiontests/templates/templates/broken_base.html b/parts/django/tests/regressiontests/templates/templates/broken_base.html
new file mode 100644
index 0000000..aa41f44
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templates/broken_base.html
@@ -0,0 +1 @@
+{% include "missing.html" %}
diff --git a/parts/django/tests/regressiontests/templates/templates/first/test.html b/parts/django/tests/regressiontests/templates/templates/first/test.html
new file mode 100644
index 0000000..6029fe5
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templates/first/test.html
@@ -0,0 +1 @@
+First template
diff --git a/parts/django/tests/regressiontests/templates/templates/second/test.html b/parts/django/tests/regressiontests/templates/templates/second/test.html
new file mode 100644
index 0000000..d9b316f
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templates/second/test.html
@@ -0,0 +1 @@
+Second template
diff --git a/parts/django/tests/regressiontests/templates/templates/test_extends_error.html b/parts/django/tests/regressiontests/templates/templates/test_extends_error.html
new file mode 100755
index 0000000..fc74690
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templates/test_extends_error.html
@@ -0,0 +1 @@
+{% extends "broken_base.html" %}
diff --git a/parts/django/tests/regressiontests/templates/templatetags/__init__.py b/parts/django/tests/regressiontests/templates/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templatetags/__init__.py
diff --git a/parts/django/tests/regressiontests/templates/templatetags/broken_tag.py b/parts/django/tests/regressiontests/templates/templatetags/broken_tag.py
new file mode 100644
index 0000000..c70e183
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templatetags/broken_tag.py
@@ -0,0 +1 @@
+from django import Xtemplate
diff --git a/parts/django/tests/regressiontests/templates/templatetags/custom.py b/parts/django/tests/regressiontests/templates/templatetags/custom.py
new file mode 100644
index 0000000..fdf8d10
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/templatetags/custom.py
@@ -0,0 +1,11 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+
+register = template.Library()
+
+def trim(value, num):
+ return value[:num]
+trim = stringfilter(trim)
+
+register.filter(trim)
+
diff --git a/parts/django/tests/regressiontests/templates/tests.py b/parts/django/tests/regressiontests/templates/tests.py
new file mode 100644
index 0000000..62b2237
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/tests.py
@@ -0,0 +1,1389 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+
+if __name__ == '__main__':
+ # When running this file in isolation, we need to set up the configuration
+ # before importing 'template'.
+ settings.configure()
+
+from datetime import datetime, timedelta
+import time
+import os
+import sys
+import traceback
+import unittest
+
+from django import template
+from django.core import urlresolvers
+from django.template import loader
+from django.template.loaders import app_directories, filesystem, cached
+from django.utils.translation import activate, deactivate, ugettext as _
+from django.utils.safestring import mark_safe
+from django.utils.tzinfo import LocalTimezone
+
+from context import ContextTests
+from custom import CustomTests
+from parser import ParserTests
+from unicode import UnicodeTests
+from nodelist import NodelistTest
+from smartif import *
+
+try:
+ from loaders import *
+except ImportError:
+ pass # If setuptools isn't installed, that's fine. Just move on.
+
+import filters
+
+#################################
+# Custom template tag for tests #
+#################################
+
+register = template.Library()
+
+class EchoNode(template.Node):
+ def __init__(self, contents):
+ self.contents = contents
+
+ def render(self, context):
+ return " ".join(self.contents)
+
+def do_echo(parser, token):
+ return EchoNode(token.contents.split()[1:])
+
+register.tag("echo", do_echo)
+
+template.libraries['testtags'] = register
+
+#####################################
+# Helper objects for template tests #
+#####################################
+
+class SomeException(Exception):
+ silent_variable_failure = True
+
+class SomeOtherException(Exception):
+ pass
+
+class ContextStackException(Exception):
+ pass
+
+class SomeClass:
+ def __init__(self):
+ self.otherclass = OtherClass()
+
+ def method(self):
+ return "SomeClass.method"
+
+ def method2(self, o):
+ return o
+
+ def method3(self):
+ raise SomeException
+
+ def method4(self):
+ raise SomeOtherException
+
+class OtherClass:
+ def method(self):
+ return "OtherClass.method"
+
+class TestObj(object):
+ def is_true(self):
+ return True
+
+ def is_false(self):
+ return False
+
+ def is_bad(self):
+ time.sleep(0.3)
+ return True
+
+class SilentGetItemClass(object):
+ def __getitem__(self, key):
+ raise SomeException
+
+class SilentAttrClass(object):
+ def b(self):
+ raise SomeException
+ b = property(b)
+
+class UTF8Class:
+ "Class whose __str__ returns non-ASCII data"
+ def __str__(self):
+ return u'ŠĐĆŽćžšđ'.encode('utf-8')
+
+class Templates(unittest.TestCase):
+ def test_loaders_security(self):
+ ad_loader = app_directories.Loader()
+ fs_loader = filesystem.Loader()
+ def test_template_sources(path, template_dirs, expected_sources):
+ if isinstance(expected_sources, list):
+ # Fix expected sources so they are normcased and abspathed
+ expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources]
+ # Test the two loaders (app_directores and filesystem).
+ func1 = lambda p, t: list(ad_loader.get_template_sources(p, t))
+ func2 = lambda p, t: list(fs_loader.get_template_sources(p, t))
+ for func in (func1, func2):
+ if isinstance(expected_sources, list):
+ self.assertEqual(func(path, template_dirs), expected_sources)
+ else:
+ self.assertRaises(expected_sources, func, path, template_dirs)
+
+ template_dirs = ['/dir1', '/dir2']
+ test_template_sources('index.html', template_dirs,
+ ['/dir1/index.html', '/dir2/index.html'])
+ test_template_sources('/etc/passwd', template_dirs, [])
+ test_template_sources('etc/passwd', template_dirs,
+ ['/dir1/etc/passwd', '/dir2/etc/passwd'])
+ test_template_sources('../etc/passwd', template_dirs, [])
+ test_template_sources('../../../etc/passwd', template_dirs, [])
+ test_template_sources('/dir1/index.html', template_dirs,
+ ['/dir1/index.html'])
+ test_template_sources('../dir2/index.html', template_dirs,
+ ['/dir2/index.html'])
+ test_template_sources('/dir1blah', template_dirs, [])
+ test_template_sources('../dir1blah', template_dirs, [])
+
+ # UTF-8 bytestrings are permitted.
+ test_template_sources('\xc3\x85ngstr\xc3\xb6m', template_dirs,
+ [u'/dir1/Ångström', u'/dir2/Ångström'])
+ # Unicode strings are permitted.
+ test_template_sources(u'Ångström', template_dirs,
+ [u'/dir1/Ångström', u'/dir2/Ångström'])
+ test_template_sources(u'Ångström', ['/Straße'], [u'/Straße/Ångström'])
+ test_template_sources('\xc3\x85ngstr\xc3\xb6m', ['/Straße'],
+ [u'/Straße/Ångström'])
+ # Invalid UTF-8 encoding in bytestrings is not. Should raise a
+ # semi-useful error message.
+ test_template_sources('\xc3\xc3', template_dirs, UnicodeDecodeError)
+
+ # Case insensitive tests (for win32). Not run unless we're on
+ # a case insensitive operating system.
+ if os.path.normcase('/TEST') == os.path.normpath('/test'):
+ template_dirs = ['/dir1', '/DIR2']
+ test_template_sources('index.html', template_dirs,
+ ['/dir1/index.html', '/dir2/index.html'])
+ test_template_sources('/DIR1/index.HTML', template_dirs,
+ ['/dir1/index.html'])
+
+ def test_loader_debug_origin(self):
+ # Turn TEMPLATE_DEBUG on, so that the origin file name will be kept with
+ # the compiled templates.
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True
+ old_loaders = loader.template_source_loaders
+
+ try:
+ loader.template_source_loaders = (filesystem.Loader(),)
+
+ # We rely on the fact that runtests.py sets up TEMPLATE_DIRS to
+ # point to a directory containing a 404.html file. Also that
+ # the file system and app directories loaders both inherit the
+ # load_template method from the BaseLoader class, so we only need
+ # to test one of them.
+ load_name = '404.html'
+ template = loader.get_template(load_name)
+ template_name = template.nodelist[0].source[0].name
+ self.assertTrue(template_name.endswith(load_name),
+ 'Template loaded by filesystem loader has incorrect name for debug page: %s' % template_name)
+
+ # Aso test the cached loader, since it overrides load_template
+ cache_loader = cached.Loader(('',))
+ cache_loader._cached_loaders = loader.template_source_loaders
+ loader.template_source_loaders = (cache_loader,)
+
+ template = loader.get_template(load_name)
+ template_name = template.nodelist[0].source[0].name
+ self.assertTrue(template_name.endswith(load_name),
+ 'Template loaded through cached loader has incorrect name for debug page: %s' % template_name)
+
+ template = loader.get_template(load_name)
+ template_name = template.nodelist[0].source[0].name
+ self.assertTrue(template_name.endswith(load_name),
+ 'Cached template loaded through cached loader has incorrect name for debug page: %s' % template_name)
+ finally:
+ loader.template_source_loaders = old_loaders
+ settings.TEMPLATE_DEBUG = old_td
+
+ def test_extends_include_missing_baseloader(self):
+ """
+ Tests that the correct template is identified as not existing
+ when {% extends %} specifies a template that does exist, but
+ that template has an {% include %} of something that does not
+ exist. See #12787.
+ """
+
+ # TEMPLATE_DEBUG must be true, otherwise the exception raised
+ # during {% include %} processing will be suppressed.
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True
+ old_loaders = loader.template_source_loaders
+
+ try:
+ # Test the base loader class via the app loader. load_template
+ # from base is used by all shipped loaders excepting cached,
+ # which has its own test.
+ loader.template_source_loaders = (app_directories.Loader(),)
+
+ load_name = 'test_extends_error.html'
+ tmpl = loader.get_template(load_name)
+ r = None
+ try:
+ r = tmpl.render(template.Context({}))
+ except template.TemplateSyntaxError, e:
+ settings.TEMPLATE_DEBUG = old_td
+ self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
+ self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
+ finally:
+ loader.template_source_loaders = old_loaders
+ settings.TEMPLATE_DEBUG = old_td
+
+ def test_extends_include_missing_cachedloader(self):
+ """
+ Same as test_extends_include_missing_baseloader, only tests
+ behavior of the cached loader instead of BaseLoader.
+ """
+
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True
+ old_loaders = loader.template_source_loaders
+
+ try:
+ cache_loader = cached.Loader(('',))
+ cache_loader._cached_loaders = (app_directories.Loader(),)
+ loader.template_source_loaders = (cache_loader,)
+
+ load_name = 'test_extends_error.html'
+ tmpl = loader.get_template(load_name)
+ r = None
+ try:
+ r = tmpl.render(template.Context({}))
+ except template.TemplateSyntaxError, e:
+ self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
+ self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
+
+ # For the cached loader, repeat the test, to ensure the first attempt did not cache a
+ # result that behaves incorrectly on subsequent attempts.
+ tmpl = loader.get_template(load_name)
+ try:
+ tmpl.render(template.Context({}))
+ except template.TemplateSyntaxError, e:
+ self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
+ self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
+ finally:
+ loader.template_source_loaders = old_loaders
+ settings.TEMPLATE_DEBUG = old_td
+
+ def test_token_smart_split(self):
+ # Regression test for #7027
+ token = template.Token(template.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
+ split = token.split_contents()
+ self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")'])
+
+ def test_url_reverse_no_settings_module(self):
+ # Regression test for #9005
+ from django.template import Template, Context, TemplateSyntaxError
+
+ old_settings_module = settings.SETTINGS_MODULE
+ old_template_debug = settings.TEMPLATE_DEBUG
+
+ settings.SETTINGS_MODULE = None
+ settings.TEMPLATE_DEBUG = True
+
+ t = Template('{% url will_not_match %}')
+ c = Context()
+ try:
+ rendered = t.render(c)
+ except TemplateSyntaxError, e:
+ # Assert that we are getting the template syntax error and not the
+ # string encoding error.
+ self.assertEquals(e.args[0], "Caught NoReverseMatch while rendering: Reverse for 'will_not_match' with arguments '()' and keyword arguments '{}' not found.")
+
+ settings.SETTINGS_MODULE = old_settings_module
+ settings.TEMPLATE_DEBUG = old_template_debug
+
+ def test_invalid_block_suggestion(self):
+ # See #7876
+ from django.template import Template, TemplateSyntaxError
+ try:
+ t = Template("{% if 1 %}lala{% endblock %}{% endif %}")
+ except TemplateSyntaxError, e:
+ self.assertEquals(e.args[0], "Invalid block tag: 'endblock', expected 'else' or 'endif'")
+
+ def test_templates(self):
+ template_tests = self.get_template_tests()
+ filter_tests = filters.get_filter_tests()
+
+ # Quickly check that we aren't accidentally using a name in both
+ # template and filter tests.
+ overlapping_names = [name for name in filter_tests if name in template_tests]
+ assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names)
+
+ template_tests.update(filter_tests)
+
+ # Register our custom template loader.
+ def test_template_loader(template_name, template_dirs=None):
+ "A custom template loader that loads the unit-test templates."
+ try:
+ return (template_tests[template_name][0] , "test:%s" % template_name)
+ except KeyError:
+ raise template.TemplateDoesNotExist, template_name
+
+ cache_loader = cached.Loader(('test_template_loader',))
+ cache_loader._cached_loaders = (test_template_loader,)
+
+ old_template_loaders = loader.template_source_loaders
+ loader.template_source_loaders = [cache_loader]
+
+ failures = []
+ tests = template_tests.items()
+ tests.sort()
+
+ # Turn TEMPLATE_DEBUG off, because tests assume that.
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
+
+ # Set TEMPLATE_STRING_IF_INVALID to a known string.
+ old_invalid = settings.TEMPLATE_STRING_IF_INVALID
+ expected_invalid_str = 'INVALID'
+
+ # Warm the URL reversing cache. This ensures we don't pay the cost
+ # warming the cache during one of the tests.
+ urlresolvers.reverse('regressiontests.templates.views.client_action',
+ kwargs={'id':0,'action':"update"})
+
+ for name, vals in tests:
+ if isinstance(vals[2], tuple):
+ normal_string_result = vals[2][0]
+ invalid_string_result = vals[2][1]
+ if isinstance(invalid_string_result, basestring) and '%s' in invalid_string_result:
+ expected_invalid_str = 'INVALID %s'
+ invalid_string_result = invalid_string_result % vals[2][2]
+ template.invalid_var_format_string = True
+ else:
+ normal_string_result = vals[2]
+ invalid_string_result = vals[2]
+
+ if 'LANGUAGE_CODE' in vals[1]:
+ activate(vals[1]['LANGUAGE_CODE'])
+ else:
+ activate('en-us')
+
+ for invalid_str, result in [('', normal_string_result),
+ (expected_invalid_str, invalid_string_result)]:
+ settings.TEMPLATE_STRING_IF_INVALID = invalid_str
+ for is_cached in (False, True):
+ try:
+ start = datetime.now()
+ test_template = loader.get_template(name)
+ end = datetime.now()
+ if end-start > timedelta(seconds=0.2):
+ failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Took too long to parse test" % (is_cached, invalid_str, name))
+
+ start = datetime.now()
+ output = self.render(test_template, vals)
+ end = datetime.now()
+ if end-start > timedelta(seconds=0.2):
+ failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Took too long to render test" % (is_cached, invalid_str, name))
+ except ContextStackException:
+ failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (is_cached, invalid_str, name))
+ continue
+ except Exception:
+ exc_type, exc_value, exc_tb = sys.exc_info()
+ if exc_type != result:
+ tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
+ failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s\n%s" % (is_cached, invalid_str, name, exc_type, exc_value, tb))
+ continue
+ if output != result:
+ failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (is_cached, invalid_str, name, result, output))
+ cache_loader.reset()
+
+ if 'LANGUAGE_CODE' in vals[1]:
+ deactivate()
+
+ if template.invalid_var_format_string:
+ expected_invalid_str = 'INVALID'
+ template.invalid_var_format_string = False
+
+ loader.template_source_loaders = old_template_loaders
+ deactivate()
+ settings.TEMPLATE_DEBUG = old_td
+ settings.TEMPLATE_STRING_IF_INVALID = old_invalid
+
+ self.assertEqual(failures, [], "Tests failed:\n%s\n%s" %
+ ('-'*70, ("\n%s\n" % ('-'*70)).join(failures)))
+
+ def render(self, test_template, vals):
+ context = template.Context(vals[1])
+ before_stack_size = len(context.dicts)
+ output = test_template.render(context)
+ if len(context.dicts) != before_stack_size:
+ raise ContextStackException
+ return output
+
+ def get_template_tests(self):
+ # SYNTAX --
+ # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
+ return {
+ ### BASIC SYNTAX ################################################
+
+ # Plain text should go through the template parser untouched
+ 'basic-syntax01': ("something cool", {}, "something cool"),
+
+ # Variables should be replaced with their value in the current
+ # context
+ 'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
+
+ # More than one replacement variable is allowed in a template
+ 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"),
+
+ # Fail silently when a variable is not found in the current context
+ 'basic-syntax04': ("as{{ missing }}df", {}, ("asdf","asINVALIDdf")),
+
+ # A variable may not contain more than one word
+ 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for empty variable tags
+ 'basic-syntax07': ("{{ }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax08': ("{{ }}", {}, template.TemplateSyntaxError),
+
+ # Attribute syntax allows a template to call an object's attribute
+ 'basic-syntax09': ("{{ var.method }}", {"var": SomeClass()}, "SomeClass.method"),
+
+ # Multiple levels of attribute access are allowed
+ 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"),
+
+ # Fail silently when a variable's attribute isn't found
+ 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ("","INVALID")),
+
+ # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
+ 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError when trying to access a variable containing an illegal character
+ 'basic-syntax13': ("{{ va>r }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax14': ("{{ (var.r) }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax15': ("{{ sp%am }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax16': ("{{ eggs! }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax17': ("{{ moo? }}", {}, template.TemplateSyntaxError),
+
+ # Attribute syntax allows a template to call a dictionary key's value
+ 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"),
+
+ # Fail silently when a variable's dictionary key isn't found
+ 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ("","INVALID")),
+
+ # Fail silently when accessing a non-simple method
+ 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ("","INVALID")),
+
+ # Don't get confused when parsing something that is almost, but not
+ # quite, a template tag.
+ 'basic-syntax21': ("a {{ moo %} b", {}, "a {{ moo %} b"),
+ 'basic-syntax22': ("{{ moo #}", {}, "{{ moo #}"),
+
+ # Will try to treat "moo #} {{ cow" as the variable. Not ideal, but
+ # costly to work around, so this triggers an error.
+ 'basic-syntax23': ("{{ moo #} {{ cow }}", {"cow": "cow"}, template.TemplateSyntaxError),
+
+ # Embedded newlines make it not-a-tag.
+ 'basic-syntax24': ("{{ moo\n }}", {}, "{{ moo\n }}"),
+
+ # Literal strings are permitted inside variables, mostly for i18n
+ # purposes.
+ 'basic-syntax25': ('{{ "fred" }}', {}, "fred"),
+ 'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""),
+ 'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""),
+
+ # regression test for ticket #12554
+ # make sure a silent_variable_failure Exception is supressed
+ # on dictionary and attribute lookup
+ 'basic-syntax28': ("{{ a.b }}", {'a': SilentGetItemClass()}, ('', 'INVALID')),
+ 'basic-syntax29': ("{{ a.b }}", {'a': SilentAttrClass()}, ('', 'INVALID')),
+
+ # Something that starts like a number but has an extra lookup works as a lookup.
+ 'basic-syntax30': ("{{ 1.2.3 }}", {"1": {"2": {"3": "d"}}}, "d"),
+ 'basic-syntax31': ("{{ 1.2.3 }}", {"1": {"2": ("a", "b", "c", "d")}}, "d"),
+ 'basic-syntax32': ("{{ 1.2.3 }}", {"1": (("x", "x", "x", "x"), ("y", "y", "y", "y"), ("a", "b", "c", "d"))}, "d"),
+ 'basic-syntax33': ("{{ 1.2.3 }}", {"1": ("xxxx", "yyyy", "abcd")}, "d"),
+ 'basic-syntax34': ("{{ 1.2.3 }}", {"1": ({"x": "x"}, {"y": "y"}, {"z": "z", "3": "d"})}, "d"),
+
+ # Numbers are numbers even if their digits are in the context.
+ 'basic-syntax35': ("{{ 1 }}", {"1": "abc"}, "1"),
+ 'basic-syntax36': ("{{ 1.2 }}", {"1": "abc"}, "1.2"),
+
+ # List-index syntax allows a template to access a certain item of a subscriptable object.
+ 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),
+
+ # Fail silently when the list index is out of range.
+ 'list-index02': ("{{ var.5 }}", {"var": ["first item", "second item"]}, ("", "INVALID")),
+
+ # Fail silently when the variable is not a subscriptable object.
+ 'list-index03': ("{{ var.1 }}", {"var": None}, ("", "INVALID")),
+
+ # Fail silently when variable is a dict without the specified key.
+ 'list-index04': ("{{ var.1 }}", {"var": {}}, ("", "INVALID")),
+
+ # Dictionary lookup wins out when dict's key is a string.
+ 'list-index05': ("{{ var.1 }}", {"var": {'1': "hello"}}, "hello"),
+
+ # But list-index lookup wins out when dict's key is an int, which
+ # behind the scenes is really a dictionary lookup (for a dict)
+ # after converting the key to an int.
+ 'list-index06': ("{{ var.1 }}", {"var": {1: "hello"}}, "hello"),
+
+ # Dictionary lookup wins out when there is a string and int version of the key.
+ 'list-index07': ("{{ var.1 }}", {"var": {'1': "hello", 1: "world"}}, "hello"),
+
+ # Basic filter usage
+ 'filter-syntax01': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
+
+ # Chained filters
+ 'filter-syntax02': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
+
+ # Raise TemplateSyntaxError for space between a variable and filter pipe
+ 'filter-syntax03': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for space after a filter pipe
+ 'filter-syntax04': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for a nonexistent filter
+ 'filter-syntax05': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError when trying to access a filter containing an illegal character
+ 'filter-syntax06': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for invalid block tags
+ 'filter-syntax07': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for empty block tags
+ 'filter-syntax08': ("{% %}", {}, template.TemplateSyntaxError),
+
+ # Chained filters, with an argument to the first one
+ 'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
+
+ # Literal string as argument is always "safe" from auto-escaping..
+ 'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}',
+ {"var": None}, ' endquote" hah'),
+
+ # Variable as argument
+ 'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
+
+ # Default argument testing
+ 'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
+
+ # Fail silently for methods that raise an exception with a
+ # "silent_variable_failure" attribute
+ 'filter-syntax13': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
+
+ # In methods that raise an exception without a
+ # "silent_variable_attribute" set to True, the exception propagates
+ 'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
+
+ # Escaped backslash in argument
+ 'filter-syntax15': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
+
+ # Escaped backslash using known escape char
+ 'filter-syntax16': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
+
+ # Empty strings can be passed as arguments to filters
+ 'filter-syntax17': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
+
+ # Make sure that any unicode strings are converted to bytestrings
+ # in the final output.
+ 'filter-syntax18': (r'{{ var }}', {'var': UTF8Class()}, u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'),
+
+ # Numbers as filter arguments should work
+ 'filter-syntax19': ('{{ var|truncatewords:1 }}', {"var": "hello world"}, "hello ..."),
+
+ #filters should accept empty string constants
+ 'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""),
+
+ ### COMMENT SYNTAX ########################################################
+ 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
+ 'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),
+
+ # Comments can contain invalid stuff.
+ 'comment-syntax03': ("foo{# {% if %} #}", {}, "foo"),
+ 'comment-syntax04': ("foo{# {% endblock %} #}", {}, "foo"),
+ 'comment-syntax05': ("foo{# {% somerandomtag %} #}", {}, "foo"),
+ 'comment-syntax06': ("foo{# {% #}", {}, "foo"),
+ 'comment-syntax07': ("foo{# %} #}", {}, "foo"),
+ 'comment-syntax08': ("foo{# %} #}bar", {}, "foobar"),
+ 'comment-syntax09': ("foo{# {{ #}", {}, "foo"),
+ 'comment-syntax10': ("foo{# }} #}", {}, "foo"),
+ 'comment-syntax11': ("foo{# { #}", {}, "foo"),
+ 'comment-syntax12': ("foo{# } #}", {}, "foo"),
+
+ ### COMMENT TAG ###########################################################
+ 'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
+ 'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
+
+ # Comment tag can contain invalid stuff.
+ 'comment-tag03': ("foo{% comment %} {% if %} {% endcomment %}", {}, "foo"),
+ 'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
+ 'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
+
+ ### CYCLE TAG #############################################################
+ 'cycle01': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
+ 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
+ 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
+ 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
+ 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
+ 'cycle08': ('{% cycle a,b,c as foo %}{% cycle foo %}{{ foo }}{{ foo }}{% cycle foo %}{{ foo }}', {}, 'abbbcc'),
+ 'cycle09': ("{% for i in test %}{% cycle a,b %}{{ i }},{% endfor %}", {'test': range(5)}, 'a0,b1,a2,b3,a4,'),
+ 'cycle10': ("{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}", {}, 'ab'),
+ 'cycle11': ("{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}{% cycle abc %}", {}, 'abc'),
+ 'cycle12': ("{% cycle 'a' 'b' 'c' as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}", {}, 'abca'),
+ 'cycle13': ("{% for i in test %}{% cycle 'a' 'b' %}{{ i }},{% endfor %}", {'test': range(5)}, 'a0,b1,a2,b3,a4,'),
+ 'cycle14': ("{% cycle one two as foo %}{% cycle foo %}", {'one': '1','two': '2'}, '12'),
+ 'cycle15': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'),
+ 'cycle16': ("{% cycle one|lower two as foo %}{% cycle foo %}", {'one': 'A','two': '2'}, 'a2'),
+
+ ### EXCEPTIONS ############################################################
+
+ # Raise exception for invalid template name
+ 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateDoesNotExist),
+
+ # Raise exception for invalid template name (in variable)
+ 'exception02': ("{% extends nonexistent %}", {}, (template.TemplateSyntaxError, template.TemplateDoesNotExist)),
+
+ # Raise exception for extra {% extends %} tags
+ 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
+ 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
+
+ ### FILTER TAG ############################################################
+ 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
+ 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
+ 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
+ 'filter04': ('{% filter cut:remove %}djangospam{% endfilter %}', {'remove': 'spam'}, 'django'),
+
+ ### FIRSTOF TAG ###########################################################
+ 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
+ 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
+ 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
+ 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
+ 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
+ 'firstof06': ('{% firstof a b c %}', {'b':0,'c':3}, '3'),
+ 'firstof07': ('{% firstof a b "c" %}', {'a':0}, 'c'),
+ 'firstof08': ('{% firstof a b "c and d" %}', {'a':0,'b':0}, 'c and d'),
+ 'firstof09': ('{% firstof %}', {}, template.TemplateSyntaxError),
+
+ ### FOR TAG ###############################################################
+ 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
+ 'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
+ 'for-tag-vars01': ("{% for val in values %}{{ forloop.counter }}{% endfor %}", {"values": [6, 6, 6]}, "123"),
+ 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
+ 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
+ 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
+ 'for-tag-vars05': ("{% for val in values %}{% if forloop.first %}f{% else %}x{% endif %}{% endfor %}", {"values": [6, 6, 6]}, "fxx"),
+ 'for-tag-vars06': ("{% for val in values %}{% if forloop.last %}l{% else %}x{% endif %}{% endfor %}", {"values": [6, 6, 6]}, "xxl"),
+ 'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
+ 'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
+ 'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
+ # Ensure that a single loopvar doesn't truncate the list in val.
+ 'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items.
+ 'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"),
+ 'for-tag-unpack11': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")),
+ 'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")),
+ 'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")),
+ 'for-tag-unpack14': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (1, 2)}, (":/:/", "INVALID:INVALID/INVALID:INVALID/")),
+ 'for-tag-empty01': ("{% for val in values %}{{ val }}{% empty %}empty text{% endfor %}", {"values": [1, 2, 3]}, "123"),
+ 'for-tag-empty02': ("{% for val in values %}{{ val }}{% empty %}values array empty{% endfor %}", {"values": []}, "values array empty"),
+ 'for-tag-empty03': ("{% for val in values %}{{ val }}{% empty %}values array not found{% endfor %}", {}, "values array not found"),
+
+ ### IF TAG ################################################################
+ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
+ 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
+ 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # Filters
+ 'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% endif %}", {'foo': 'abcde'}, "yes"),
+ 'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # Equality
+ 'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1}, "no"),
+ 'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 1}, "yes"),
+ 'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 2}, "no"),
+ 'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # Comparison
+ 'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, "no"),
+ 'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, "no"),
+ 'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, "no"),
+ 'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-lte-02': ("{% if 2 <= 1 %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # Contains
+ 'if-tag-in-01': ("{% if 1 in x %}yes{% else %}no{% endif %}", {'x':[1]}, "yes"),
+ 'if-tag-in-02': ("{% if 2 in x %}yes{% else %}no{% endif %}", {'x':[1]}, "no"),
+ 'if-tag-not-in-01': ("{% if 1 not in x %}yes{% else %}no{% endif %}", {'x':[1]}, "no"),
+ 'if-tag-not-in-02': ("{% if 2 not in x %}yes{% else %}no{% endif %}", {'x':[1]}, "yes"),
+
+ # AND
+ 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-and03': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-and04': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-and05': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-and06': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-and07': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-and08': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': True}, 'no'),
+
+ # OR
+ 'if-tag-or01': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-or02': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-or03': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-or04': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-or05': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-or06': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
+
+ # multiple ORs
+ 'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif %}", {'baz': True}, 'yes'),
+
+ # NOT
+ 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'no'),
+ # not03 to not05 removed, now TemplateSyntaxErrors
+
+ 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not08': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not09': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not10': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not11': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not12': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not13': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not14': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not15': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not16': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not17': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not18': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not19': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not20': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not21': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not22': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not23': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not24': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not25': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not26': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not27': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not28': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not29': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not30': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not31': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not32': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not33': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ # Various syntax errors
+ 'if-tag-error01': ("{% if %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error07': ("{% if not %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error08': ("{% if and %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error09': ("{% if or %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error10': ("{% if == %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, template.TemplateSyntaxError),
+
+ # If evaluations are shortcircuited where possible
+ # These tests will fail by taking too long to run. When the if clause
+ # is shortcircuiting correctly, the is_bad() function shouldn't be
+ # evaluated, and the deliberate sleep won't happen.
+ 'if-tag-shortcircuit01': ('{% if x.is_true or x.is_bad %}yes{% else %}no{% endif %}', {'x': TestObj()}, "yes"),
+ 'if-tag-shortcircuit02': ('{% if x.is_false and x.is_bad %}yes{% else %}no{% endif %}', {'x': TestObj()}, "no"),
+
+ # Non-existent args
+ 'if-tag-badarg01':("{% if x|default_if_none:y %}yes{% endif %}", {}, ''),
+ 'if-tag-badarg02':("{% if x|default_if_none:y %}yes{% endif %}", {'y': 0}, ''),
+ 'if-tag-badarg03':("{% if x|default_if_none:y %}yes{% endif %}", {'y': 1}, 'yes'),
+ 'if-tag-badarg04':("{% if x|default_if_none:y %}yes{% else %}no{% endif %}", {}, 'no'),
+
+ # Additional, more precise parsing tests are in SmartIfTests
+
+ ### IFCHANGED TAG #########################################################
+ 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'),
+ 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,1,3)}, '13'),
+ 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,1,1)}, '1'),
+ 'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'),
+ 'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
+ 'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
+ 'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
+ 'ifchanged08': ('{% for data in datalist %}{% for c,d in data %}{% if c %}{% ifchanged %}{{ d }}{% endifchanged %}{% endif %}{% endfor %}{% endfor %}', {'datalist': [[(1, 'a'), (1, 'a'), (0, 'b'), (1, 'c')], [(0, 'a'), (1, 'c'), (1, 'd'), (1, 'd'), (0, 'e')]]}, 'accd'),
+
+ # Test one parameter given to ifchanged.
+ 'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
+ 'ifchanged-param02': ('{% for n in num %}{% for x in numx %}{% ifchanged n %}..{% endifchanged %}{{ x }}{% endfor %}{% endfor %}', { 'num': (1,2,3), 'numx': (5,6,7) }, '..567..567..567'),
+
+ # Test multiple parameters to ifchanged.
+ 'ifchanged-param03': ('{% for n in num %}{{ n }}{% for x in numx %}{% ifchanged x n %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1,1,2), 'numx': (5,6,6) }, '156156256'),
+
+ # Test a date+hour like construct, where the hour of the last day
+ # is the same but the date had changed, so print the hour anyway.
+ 'ifchanged-param04': ('{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
+
+ # Logically the same as above, just written with explicit
+ # ifchanged for the day.
+ 'ifchanged-param05': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
+
+ # Test the else clause of ifchanged.
+ 'ifchanged-else01': ('{% for id in ids %}{{ id }}{% ifchanged id %}-first{% else %}-other{% endifchanged %},{% endfor %}', {'ids': [1,1,2,2,2,3]}, '1-first,1-other,2-first,2-other,2-other,3-first,'),
+
+ 'ifchanged-else02': ('{% for id in ids %}{{ id }}-{% ifchanged id %}{% cycle red,blue %}{% else %}grey{% endifchanged %},{% endfor %}', {'ids': [1,1,2,2,2,3]}, '1-red,1-grey,2-blue,2-grey,2-grey,3-red,'),
+ 'ifchanged-else03': ('{% for id in ids %}{{ id }}{% ifchanged id %}-{% cycle red,blue %}{% else %}{% endifchanged %},{% endfor %}', {'ids': [1,1,2,2,2,3]}, '1-red,1,2-blue,2,2,3-red,'),
+
+ 'ifchanged-else04': ('{% for id in ids %}{% ifchanged %}***{{ id }}*{% else %}...{% endifchanged %}{{ forloop.counter }}{% endfor %}', {'ids': [1,1,2,2,2,3,4]}, '***1*1...2***2*3...4...5***3*6***4*7'),
+
+ ### IFEQUAL TAG ###########################################################
+ 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
+ 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
+ 'ifequal03': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 2}, "no"),
+ 'ifequal04': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 1}, "yes"),
+ 'ifequal05': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "test"}, "yes"),
+ 'ifequal06': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "no"}, "no"),
+ 'ifequal07': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "test"}, "yes"),
+ 'ifequal08': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "no"}, "no"),
+ 'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
+ 'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
+
+ # SMART SPLITTING
+ 'ifequal-split01': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {}, "no"),
+ 'ifequal-split02': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'foo'}, "no"),
+ 'ifequal-split03': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'test man'}, "yes"),
+ 'ifequal-split04': ("{% ifequal a 'test man' %}yes{% else %}no{% endifequal %}", {'a': 'test man'}, "yes"),
+ 'ifequal-split05': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': ''}, "no"),
+ 'ifequal-split06': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i "love" you'}, "yes"),
+ 'ifequal-split07': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i love you'}, "no"),
+ 'ifequal-split08': (r"{% ifequal a 'I\'m happy' %}yes{% else %}no{% endifequal %}", {'a': "I'm happy"}, "yes"),
+ 'ifequal-split09': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slash\man"}, "yes"),
+ 'ifequal-split10': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slashman"}, "no"),
+
+ # NUMERIC RESOLUTION
+ 'ifequal-numeric01': ('{% ifequal x 5 %}yes{% endifequal %}', {'x': '5'}, ''),
+ 'ifequal-numeric02': ('{% ifequal x 5 %}yes{% endifequal %}', {'x': 5}, 'yes'),
+ 'ifequal-numeric03': ('{% ifequal x 5.2 %}yes{% endifequal %}', {'x': 5}, ''),
+ 'ifequal-numeric04': ('{% ifequal x 5.2 %}yes{% endifequal %}', {'x': 5.2}, 'yes'),
+ 'ifequal-numeric05': ('{% ifequal x 0.2 %}yes{% endifequal %}', {'x': .2}, 'yes'),
+ 'ifequal-numeric06': ('{% ifequal x .2 %}yes{% endifequal %}', {'x': .2}, 'yes'),
+ 'ifequal-numeric07': ('{% ifequal x 2. %}yes{% endifequal %}', {'x': 2}, ''),
+ 'ifequal-numeric08': ('{% ifequal x "5" %}yes{% endifequal %}', {'x': 5}, ''),
+ 'ifequal-numeric09': ('{% ifequal x "5" %}yes{% endifequal %}', {'x': '5'}, 'yes'),
+ 'ifequal-numeric10': ('{% ifequal x -5 %}yes{% endifequal %}', {'x': -5}, 'yes'),
+ 'ifequal-numeric11': ('{% ifequal x -5.2 %}yes{% endifequal %}', {'x': -5.2}, 'yes'),
+ 'ifequal-numeric12': ('{% ifequal x +5 %}yes{% endifequal %}', {'x': 5}, 'yes'),
+
+ # FILTER EXPRESSIONS AS ARGUMENTS
+ 'ifequal-filter01': ('{% ifequal a|upper "A" %}x{% endifequal %}', {'a': 'a'}, 'x'),
+ 'ifequal-filter02': ('{% ifequal "A" a|upper %}x{% endifequal %}', {'a': 'a'}, 'x'),
+ 'ifequal-filter03': ('{% ifequal a|upper b|upper %}x{% endifequal %}', {'a': 'x', 'b': 'X'}, 'x'),
+ 'ifequal-filter04': ('{% ifequal x|slice:"1" "a" %}x{% endifequal %}', {'x': 'aaa'}, 'x'),
+ 'ifequal-filter05': ('{% ifequal x|slice:"1"|upper "A" %}x{% endifequal %}', {'x': 'aaa'}, 'x'),
+
+ ### IFNOTEQUAL TAG ########################################################
+ 'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
+ 'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
+ 'ifnotequal03': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
+ 'ifnotequal04': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 1}, "no"),
+
+ ### INCLUDE TAG ###########################################################
+ 'include01': ('{% include "basic-syntax01" %}', {}, "something cool"),
+ 'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
+ 'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
+ 'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
+ 'include 05': ('template with a space', {}, 'template with a space'),
+ 'include06': ('{% include "include 05"%}', {}, 'template with a space'),
+
+ ### NAMED ENDBLOCKS #######################################################
+
+ # Basic test
+ 'namedendblocks01': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock first %}3", {}, '1_2_3'),
+
+ # Unbalanced blocks
+ 'namedendblocks02': ("1{% block first %}_{% block second %}2{% endblock first %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
+ 'namedendblocks03': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
+ 'namedendblocks04': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock third %}3", {}, template.TemplateSyntaxError),
+ 'namedendblocks05': ("1{% block first %}_{% block second %}2{% endblock first %}", {}, template.TemplateSyntaxError),
+
+ # Mixed named and unnamed endblocks
+ 'namedendblocks06': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock first %}3", {}, '1_2_3'),
+ 'namedendblocks07': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock %}3", {}, '1_2_3'),
+
+ ### INHERITANCE ###########################################################
+
+ # Standard template with no inheritance
+ 'inheritance01': ("1{% block first %}&{% endblock %}3{% block second %}_{% endblock %}", {}, '1&3_'),
+
+ # Standard two-level inheritance
+ 'inheritance02': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
+
+ # Three-level with no redefinitions on third level
+ 'inheritance03': ("{% extends 'inheritance02' %}", {}, '1234'),
+
+ # Two-level with no redefinitions on second level
+ 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1&3_'),
+
+ # Two-level with double quotes instead of single quotes
+ 'inheritance05': ('{% extends "inheritance02" %}', {}, '1234'),
+
+ # Three-level with variable parent-template name
+ 'inheritance06': ("{% extends foo %}", {'foo': 'inheritance02'}, '1234'),
+
+ # Two-level with one block defined, one block not defined
+ 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1&35'),
+
+ # Three-level with one block defined on this level, two blocks defined next level
+ 'inheritance08': ("{% extends 'inheritance02' %}{% block second %}5{% endblock %}", {}, '1235'),
+
+ # Three-level with second and third levels blank
+ 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1&3_'),
+
+ # Three-level with space NOT in a block -- should be ignored
+ 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1&3_'),
+
+ # Three-level with both blocks defined on this level, but none on second level
+ 'inheritance11': ("{% extends 'inheritance04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
+
+ # Three-level with this level providing one and second level providing the other
+ 'inheritance12': ("{% extends 'inheritance07' %}{% block first %}2{% endblock %}", {}, '1235'),
+
+ # Three-level with this level overriding second level
+ 'inheritance13': ("{% extends 'inheritance02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
+
+ # A block defined only in a child template shouldn't be displayed
+ 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1&3_'),
+
+ # A block within another block
+ 'inheritance15': ("{% extends 'inheritance01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
+
+ # A block within another block (level 2)
+ 'inheritance16': ("{% extends 'inheritance15' %}{% block inner %}out{% endblock %}", {}, '12out3_'),
+
+ # {% load %} tag (parent -- setup for exception04)
+ 'inheritance17': ("{% load testtags %}{% block first %}1234{% endblock %}", {}, '1234'),
+
+ # {% load %} tag (standard usage, without inheritance)
+ 'inheritance18': ("{% load testtags %}{% echo this that theother %}5678", {}, 'this that theother5678'),
+
+ # {% load %} tag (within a child template)
+ 'inheritance19': ("{% extends 'inheritance01' %}{% block first %}{% load testtags %}{% echo 400 %}5678{% endblock %}", {}, '140056783_'),
+
+ # Two-level inheritance with {{ block.super }}
+ 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1&a3_'),
+
+ # Three-level inheritance with {{ block.super }} from parent
+ 'inheritance21': ("{% extends 'inheritance02' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '12a34'),
+
+ # Three-level inheritance with {{ block.super }} from grandparent
+ 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1&a3_'),
+
+ # Three-level inheritance with {{ block.super }} from parent and grandparent
+ 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1&ab3_'),
+
+ # Inheritance from local context without use of template loader
+ 'inheritance24': ("{% extends context_template %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")}, '1234'),
+
+ # Inheritance from local context with variable parent template
+ 'inheritance25': ("{% extends context_template.1 %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': [template.Template("Wrong"), template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")]}, '1234'),
+
+ # Set up a base template to extend
+ 'inheritance26': ("no tags", {}, 'no tags'),
+
+ # Inheritance from a template that doesn't have any blocks
+ 'inheritance27': ("{% extends 'inheritance26' %}", {}, 'no tags'),
+
+ # Set up a base template with a space in it.
+ 'inheritance 28': ("{% block first %}!{% endblock %}", {}, '!'),
+
+ # Inheritance from a template with a space in its name should work.
+ 'inheritance29': ("{% extends 'inheritance 28' %}", {}, '!'),
+
+ # Base template, putting block in a conditional {% if %} tag
+ 'inheritance30': ("1{% if optional %}{% block opt %}2{% endblock %}{% endif %}3", {'optional': True}, '123'),
+
+ # Inherit from a template with block wrapped in an {% if %} tag (in parent), still gets overridden
+ 'inheritance31': ("{% extends 'inheritance30' %}{% block opt %}two{% endblock %}", {'optional': True}, '1two3'),
+ 'inheritance32': ("{% extends 'inheritance30' %}{% block opt %}two{% endblock %}", {}, '13'),
+
+ # Base template, putting block in a conditional {% ifequal %} tag
+ 'inheritance33': ("1{% ifequal optional 1 %}{% block opt %}2{% endblock %}{% endifequal %}3", {'optional': 1}, '123'),
+
+ # Inherit from a template with block wrapped in an {% ifequal %} tag (in parent), still gets overridden
+ 'inheritance34': ("{% extends 'inheritance33' %}{% block opt %}two{% endblock %}", {'optional': 1}, '1two3'),
+ 'inheritance35': ("{% extends 'inheritance33' %}{% block opt %}two{% endblock %}", {'optional': 2}, '13'),
+
+ # Base template, putting block in a {% for %} tag
+ 'inheritance36': ("{% for n in numbers %}_{% block opt %}{{ n }}{% endblock %}{% endfor %}_", {'numbers': '123'}, '_1_2_3_'),
+
+ # Inherit from a template with block wrapped in an {% for %} tag (in parent), still gets overridden
+ 'inheritance37': ("{% extends 'inheritance36' %}{% block opt %}X{% endblock %}", {'numbers': '123'}, '_X_X_X_'),
+ 'inheritance38': ("{% extends 'inheritance36' %}{% block opt %}X{% endblock %}", {}, '_'),
+
+ # The super block will still be found.
+ 'inheritance39': ("{% extends 'inheritance30' %}{% block opt %}new{{ block.super }}{% endblock %}", {'optional': True}, '1new23'),
+ 'inheritance40': ("{% extends 'inheritance33' %}{% block opt %}new{{ block.super }}{% endblock %}", {'optional': 1}, '1new23'),
+ 'inheritance41': ("{% extends 'inheritance36' %}{% block opt %}new{{ block.super }}{% endblock %}", {'numbers': '123'}, '_new1_new2_new3_'),
+
+ ### I18N ##################################################################
+
+ # {% spaceless %} tag
+ 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b><i> text </i></b>"),
+ 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b><i> text </i></b>"),
+ 'spaceless03': ("{% spaceless %}<b><i>text</i></b>{% endspaceless %}", {}, "<b><i>text</i></b>"),
+
+ # simple translation of a string delimited by '
+ 'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
+
+ # simple translation of a string delimited by "
+ 'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"),
+
+ # simple translation of a variable
+ 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u"Å"),
+
+ # simple translation of a variable and filter
+ 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u'å'),
+
+ # simple translation of a string with interpolation
+ 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"),
+
+ # simple translation of a string to german
+ 'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"),
+
+ # translation of singular form
+ 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 1}, "singular"),
+
+ # translation of plural form
+ 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 2}, "2 plural"),
+
+ # simple non-translation (only marking) of a string to german
+ 'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
+
+ # translation of a variable with a translated filter
+ 'i18n10': ('{{ bool|yesno:_("yes,no,maybe") }}', {'bool': True, 'LANGUAGE_CODE': 'de'}, 'Ja'),
+
+ # translation of a variable with a non-translated filter
+ 'i18n11': ('{{ bool|yesno:"ja,nein" }}', {'bool': True}, 'ja'),
+
+ # usage of the get_available_languages tag
+ 'i18n12': ('{% load i18n %}{% get_available_languages as langs %}{% for lang in langs %}{% ifequal lang.0 "de" %}{{ lang.0 }}{% endifequal %}{% endfor %}', {}, 'de'),
+
+ # translation of constant strings
+ 'i18n13': ('{{ _("Password") }}', {'LANGUAGE_CODE': 'de'}, 'Passwort'),
+ 'i18n14': ('{% cycle "foo" _("Password") _(\'Password\') as c %} {% cycle c %} {% cycle c %}', {'LANGUAGE_CODE': 'de'}, 'foo Passwort Passwort'),
+ 'i18n15': ('{{ absent|default:_("Password") }}', {'LANGUAGE_CODE': 'de', 'absent': ""}, 'Passwort'),
+ 'i18n16': ('{{ _("<") }}', {'LANGUAGE_CODE': 'de'}, '<'),
+
+ # Escaping inside blocktrans and trans works as if it was directly in the
+ # template.
+ 'i18n17': ('{% load i18n %}{% blocktrans with anton|escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
+ 'i18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
+ 'i18n19': ('{% load i18n %}{% blocktrans %}{{ andrew }}{% endblocktrans %}', {'andrew': 'a & b'}, u'a &amp; b'),
+ 'i18n20': ('{% load i18n %}{% trans andrew %}', {'andrew': 'a & b'}, u'a &amp; b'),
+ 'i18n21': ('{% load i18n %}{% blocktrans %}{{ andrew }}{% endblocktrans %}', {'andrew': mark_safe('a & b')}, u'a & b'),
+ 'i18n22': ('{% load i18n %}{% trans andrew %}', {'andrew': mark_safe('a & b')}, u'a & b'),
+
+ # Use filters with the {% trans %} tag, #5972
+ 'i18n23': ('{% load i18n %}{% trans "Page not found"|capfirst|slice:"6:" %}', {'LANGUAGE_CODE': 'de'}, u'nicht gefunden'),
+ 'i18n24': ("{% load i18n %}{% trans 'Page not found'|upper %}", {'LANGUAGE_CODE': 'de'}, u'SEITE NICHT GEFUNDEN'),
+ 'i18n25': ('{% load i18n %}{% trans somevar|upper %}', {'somevar': 'Page not found', 'LANGUAGE_CODE': 'de'}, u'SEITE NICHT GEFUNDEN'),
+
+ # translation of plural form with extra field in singular form (#13568)
+ 'i18n26': ('{% load i18n %}{% blocktrans with myextra_field as extra_field count number as counter %}singular {{ extra_field }}{% plural %}plural{% endblocktrans %}', {'number': 1, 'myextra_field': 'test'}, "singular test"),
+
+ # translation of singular form in russian (#14126)
+ 'i18n27': ('{% load i18n %}{% blocktrans count number as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %}', {'number': 1, 'LANGUAGE_CODE': 'ru'}, u'1 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'),
+
+ ### HANDLING OF TEMPLATE_STRING_IF_INVALID ###################################
+
+ 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),
+ 'invalidstr02': ('{{ var|default_if_none:"Foo" }}', {}, ('','INVALID')),
+ 'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
+ 'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
+ 'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
+ 'invalidstr05': ('{{ var }}', {}, ('', 'INVALID %s', 'var')),
+ 'invalidstr06': ('{{ var.prop }}', {'var': {}}, ('', 'INVALID %s', 'var.prop')),
+
+ ### MULTILINE #############################################################
+
+ 'multiline01': ("""
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """,
+ {},
+ """
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """),
+
+ ### REGROUP TAG ###########################################################
+ 'regroup01': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {'data': [ {'foo':'c', 'bar':1},
+ {'foo':'d', 'bar':1},
+ {'foo':'a', 'bar':2},
+ {'foo':'b', 'bar':2},
+ {'foo':'x', 'bar':3} ]},
+ '1:cd,2:ab,3:x,'),
+
+ # Test for silent failure when target variable isn't found
+ 'regroup02': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {}, ''),
+
+ ### TEMPLATETAG TAG #######################################################
+ 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
+ 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
+ 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
+ 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
+ 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
+ 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
+ 'templatetag07': ('{% templatetag openbrace %}', {}, '{'),
+ 'templatetag08': ('{% templatetag closebrace %}', {}, '}'),
+ 'templatetag09': ('{% templatetag openbrace %}{% templatetag openbrace %}', {}, '{{'),
+ 'templatetag10': ('{% templatetag closebrace %}{% templatetag closebrace %}', {}, '}}'),
+ 'templatetag11': ('{% templatetag opencomment %}', {}, '{#'),
+ 'templatetag12': ('{% templatetag closecomment %}', {}, '#}'),
+
+ ### WIDTHRATIO TAG ########################################################
+ 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
+ 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
+ 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
+ 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
+ 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
+
+ # 62.5 should round to 63
+ 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
+
+ # 71.4 should round to 71
+ 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
+
+ # Raise exception if we don't have 3 args, last one an integer
+ 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
+ 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+ 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, '50'),
+
+ # #10043: widthratio should allow max_width to be a variable
+ 'widthratio11': ('{% widthratio a b c %}', {'a':50,'b':100, 'c': 100}, '50'),
+
+ ### WITH TAG ########################################################
+ 'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'),
+ 'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')),
+
+ 'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
+ 'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
+
+ ### NOW TAG ########################################################
+ # Simple case
+ 'now01': ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
+
+ # Check parsing of escaped and special characters
+ 'now02': ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
+ # 'now03': ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
+ # 'now04': ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
+
+ ### URL TAG ########################################################
+ # Successes
+ 'legacyurl02': ('{% url regressiontests.templates.views.client_action id=client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'legacyurl02a': ('{% url regressiontests.templates.views.client_action client.id,"update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'legacyurl02b': ("{% url regressiontests.templates.views.client_action id=client.id,action='update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'legacyurl02c': ("{% url regressiontests.templates.views.client_action client.id,'update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'legacyurl10': ('{% url regressiontests.templates.views.client_action id=client.id,action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
+ 'legacyurl13': ('{% url regressiontests.templates.views.client_action id=client.id, action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
+ 'legacyurl14': ('{% url regressiontests.templates.views.client_action client.id, arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
+ 'legacyurl16': ('{% url regressiontests.templates.views.client_action action="update",id="1" %}', {}, '/url_tag/client/1/update/'),
+ 'legacyurl16a': ("{% url regressiontests.templates.views.client_action action='update',id='1' %}", {}, '/url_tag/client/1/update/'),
+ 'legacyurl17': ('{% url regressiontests.templates.views.client_action client_id=client.my_id,action=action %}', {'client': {'my_id': 1}, 'action': 'update'}, '/url_tag/client/1/update/'),
+
+ 'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
+ 'url02': ('{% url regressiontests.templates.views.client_action id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'url02a': ('{% url regressiontests.templates.views.client_action client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'url02b': ("{% url regressiontests.templates.views.client_action id=client.id action='update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'url02c': ("{% url regressiontests.templates.views.client_action client.id 'update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
+ 'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
+ 'url05': (u'{% url метка_оператора v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
+ 'url06': (u'{% url метка_оператора_2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
+ 'url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
+ 'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
+ 'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
+ 'url10': ('{% url regressiontests.templates.views.client_action id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
+ 'url11': ('{% url regressiontests.templates.views.client_action id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
+ 'url12': ('{% url regressiontests.templates.views.client_action id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
+ 'url13': ('{% url regressiontests.templates.views.client_action id=client.id action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
+ 'url14': ('{% url regressiontests.templates.views.client_action client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
+ 'url15': ('{% url regressiontests.templates.views.client_action 12 "test" %}', {}, '/url_tag/client/12/test/'),
+ 'url18': ('{% url regressiontests.templates.views.client "1,2" %}', {}, '/url_tag/client/1,2/'),
+
+ # Failures
+ 'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
+ 'url-fail02': ('{% url no_such_view %}', {}, urlresolvers.NoReverseMatch),
+ 'url-fail03': ('{% url regressiontests.templates.views.client %}', {}, urlresolvers.NoReverseMatch),
+ 'url-fail04': ('{% url view id, %}', {}, template.TemplateSyntaxError),
+ 'url-fail05': ('{% url view id= %}', {}, template.TemplateSyntaxError),
+ 'url-fail06': ('{% url view a.id=id %}', {}, template.TemplateSyntaxError),
+ 'url-fail07': ('{% url view a.id!id %}', {}, template.TemplateSyntaxError),
+ 'url-fail08': ('{% url view id="unterminatedstring %}', {}, template.TemplateSyntaxError),
+ 'url-fail09': ('{% url view id=", %}', {}, template.TemplateSyntaxError),
+
+ # {% url ... as var %}
+ 'url-asvar01': ('{% url regressiontests.templates.views.index as url %}', {}, ''),
+ 'url-asvar02': ('{% url regressiontests.templates.views.index as url %}{{ url }}', {}, '/url_tag/'),
+ 'url-asvar03': ('{% url no_such_view as url %}{{ url }}', {}, ''),
+
+ ### CACHE TAG ######################################################
+ 'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'),
+ 'cache02': ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'),
+ 'cache03': ('{% load cache %}{% cache 2 test %}cache03{% endcache %}', {}, 'cache03'),
+ 'cache04': ('{% load cache %}{% cache 2 test %}cache04{% endcache %}', {}, 'cache03'),
+ 'cache05': ('{% load cache %}{% cache 2 test foo %}cache05{% endcache %}', {'foo': 1}, 'cache05'),
+ 'cache06': ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 2}, 'cache06'),
+ 'cache07': ('{% load cache %}{% cache 2 test foo %}cache07{% endcache %}', {'foo': 1}, 'cache05'),
+
+ # Allow first argument to be a variable.
+ 'cache08': ('{% load cache %}{% cache time test foo %}cache08{% endcache %}', {'foo': 2, 'time': 2}, 'cache06'),
+ 'cache09': ('{% load cache %}{% cache time test foo %}cache09{% endcache %}', {'foo': 3, 'time': -1}, 'cache09'),
+ 'cache10': ('{% load cache %}{% cache time test foo %}cache10{% endcache %}', {'foo': 3, 'time': -1}, 'cache10'),
+
+ # Raise exception if we don't have at least 2 args, first one integer.
+ 'cache11': ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
+ 'cache12': ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
+ 'cache13': ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError),
+ 'cache14': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': 'fail'}, template.TemplateSyntaxError),
+ 'cache15': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': []}, template.TemplateSyntaxError),
+
+ # Regression test for #7460.
+ 'cache16': ('{% load cache %}{% cache 1 foo bar %}{% endcache %}', {'foo': 'foo', 'bar': 'with spaces'}, ''),
+
+ # Regression test for #11270.
+ 'cache17': ('{% load cache %}{% cache 10 long_cache_key poem %}Some Content{% endcache %}', {'poem': 'Oh freddled gruntbuggly/Thy micturations are to me/As plurdled gabbleblotchits/On a lurgid bee/That mordiously hath bitled out/Its earted jurtles/Into a rancid festering/Or else I shall rend thee in the gobberwarts with my blurglecruncheon/See if I dont.'}, 'Some Content'),
+
+
+ ### AUTOESCAPE TAG ##############################################
+ 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),
+ 'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
+ 'autoescape-tag03': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt;"),
+
+ # Autoescape disabling and enabling nest in a predictable way.
+ 'autoescape-tag04': ("{% autoescape off %}{{ first }} {% autoescape on%}{{ first }}{% endautoescape %}{% endautoescape %}", {"first": "<a>"}, "<a> &lt;a&gt;"),
+
+ 'autoescape-tag05': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>first</b>"}, "&lt;b&gt;first&lt;/b&gt;"),
+
+ # Strings (ASCII or unicode) already marked as "safe" are not
+ # auto-escaped
+ 'autoescape-tag06': ("{{ first }}", {"first": mark_safe("<b>first</b>")}, "<b>first</b>"),
+ 'autoescape-tag07': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": mark_safe(u"<b>Apple</b>")}, u"<b>Apple</b>"),
+
+ # Literal string arguments to filters, if used in the result, are
+ # safe.
+ 'autoescape-tag08': (r'{% autoescape on %}{{ var|default_if_none:" endquote\" hah" }}{% endautoescape %}', {"var": None}, ' endquote" hah'),
+
+ # Objects which return safe strings as their __unicode__ method
+ # won't get double-escaped.
+ 'autoescape-tag09': (r'{{ unsafe }}', {'unsafe': filters.UnsafeClass()}, 'you &amp; me'),
+ 'autoescape-tag10': (r'{{ safe }}', {'safe': filters.SafeClass()}, 'you &gt; me'),
+
+ # The "safe" and "escape" filters cannot work due to internal
+ # implementation details (fortunately, the (no)autoescape block
+ # tags can be used in those cases)
+ 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError),
+
+ # ifqeual compares unescaped vales.
+ 'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes" ),
+
+ # Arguments to filters are 'safe' and manipulate their input unescaped.
+ 'autoescape-filters01': ('{{ var|cut:"&" }}', { "var": "this & that" }, "this that" ),
+ 'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry" ),
+
+ # Literal strings are safe.
+ 'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that" ),
+
+ # Iterating over strings outputs safe characters.
+ 'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&amp;,R," ),
+
+ # Escape requirement survives lookup.
+ 'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this &amp; that" ),
+
+ }
+
+
+class TemplateTagLoading(unittest.TestCase):
+
+ def setUp(self):
+ self.old_path = sys.path[:]
+ self.old_apps = settings.INSTALLED_APPS
+ self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
+ self.old_tag_modules = template.templatetags_modules
+ template.templatetags_modules = []
+
+ def tearDown(self):
+ settings.INSTALLED_APPS = self.old_apps
+ sys.path = self.old_path
+ template.templatetags_modules = self.old_tag_modules
+
+ def test_load_error(self):
+ ttext = "{% load broken_tag %}"
+ self.assertRaises(template.TemplateSyntaxError, template.Template, ttext)
+ try:
+ template.Template(ttext)
+ except template.TemplateSyntaxError, e:
+ self.assertTrue('ImportError' in e.args[0])
+ self.assertTrue('Xtemplate' in e.args[0])
+
+ def test_load_error_egg(self):
+ ttext = "{% load broken_egg %}"
+ egg_name = '%s/tagsegg.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ settings.INSTALLED_APPS = ('tagsegg',)
+ self.assertRaises(template.TemplateSyntaxError, template.Template, ttext)
+ try:
+ template.Template(ttext)
+ except template.TemplateSyntaxError, e:
+ self.assertTrue('ImportError' in e.args[0])
+ self.assertTrue('Xtemplate' in e.args[0])
+
+ def test_load_working_egg(self):
+ ttext = "{% load working_egg %}"
+ egg_name = '%s/tagsegg.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ settings.INSTALLED_APPS = ('tagsegg',)
+ t = template.Template(ttext)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/parts/django/tests/regressiontests/templates/unicode.py b/parts/django/tests/regressiontests/templates/unicode.py
new file mode 100644
index 0000000..05b0e22
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/unicode.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from unittest import TestCase
+
+from django.template import Template, TemplateEncodingError, Context
+from django.utils.safestring import SafeData
+
+
+class UnicodeTests(TestCase):
+ def test_template(self):
+ # Templates can be created from unicode strings.
+ t1 = Template(u'ŠĐĆŽćžšđ {{ var }}')
+ # Templates can also be created from bytestrings. These are assumed to
+ # be encoded using UTF-8.
+ s = '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}'
+ t2 = Template(s)
+ s = '\x80\xc5\xc0'
+ self.assertRaises(TemplateEncodingError, Template, s)
+
+ # Contexts can be constructed from unicode or UTF-8 bytestrings.
+ c1 = Context({"var": "foo"})
+ c2 = Context({u"var": "foo"})
+ c3 = Context({"var": u"Đđ"})
+ c4 = Context({u"var": "\xc4\x90\xc4\x91"})
+
+ # Since both templates and all four contexts represent the same thing,
+ # they all render the same (and are returned as unicode objects and
+ # "safe" objects as well, for auto-escaping purposes).
+ self.assertEqual(t1.render(c3), t2.render(c3))
+ self.assertTrue(isinstance(t1.render(c3), unicode))
+ self.assertTrue(isinstance(t1.render(c3), SafeData))
diff --git a/parts/django/tests/regressiontests/templates/urls.py b/parts/django/tests/regressiontests/templates/urls.py
new file mode 100644
index 0000000..28d4133
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/urls.py
@@ -0,0 +1,17 @@
+# coding: utf-8
+from django.conf.urls.defaults import *
+from regressiontests.templates import views
+
+urlpatterns = patterns('',
+
+ # Test urls for testing reverse lookups
+ (r'^$', views.index),
+ (r'^client/([\d,]+)/$', views.client),
+ (r'^client/(?P<id>\d+)/(?P<action>[^/]+)/$', views.client_action),
+ (r'^client/(?P<client_id>\d+)/(?P<action>[^/]+)/$', views.client_action),
+ url(r'^named-client/(\d+)/$', views.client2, name="named.client"),
+
+ # Unicode strings are permitted everywhere.
+ url(ur'^Юникод/(\w+)/$', views.client2, name=u"метка_оператора"),
+ url(ur'^Юникод/(?P<tag>\S+)/$', 'regressiontests.templates.views.client2', name=u"метка_оператора_2"),
+)
diff --git a/parts/django/tests/regressiontests/templates/views.py b/parts/django/tests/regressiontests/templates/views.py
new file mode 100644
index 0000000..ca3cecd
--- /dev/null
+++ b/parts/django/tests/regressiontests/templates/views.py
@@ -0,0 +1,13 @@
+# Fake views for testing url reverse lookup
+
+def index(request):
+ pass
+
+def client(request, id):
+ pass
+
+def client_action(request, id, action):
+ pass
+
+def client2(request, tag):
+ pass
diff --git a/parts/django/tests/regressiontests/test_client_regress/__init__.py b/parts/django/tests/regressiontests/test_client_regress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/__init__.py
diff --git a/parts/django/tests/regressiontests/test_client_regress/bad_templates/404.html b/parts/django/tests/regressiontests/test_client_regress/bad_templates/404.html
new file mode 100644
index 0000000..816bcb9
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/bad_templates/404.html
@@ -0,0 +1,3 @@
+{% block foo %}
+
+This template is deliberately bad - we want it to raise an exception when it is used.
diff --git a/parts/django/tests/regressiontests/test_client_regress/fixtures/testdata.json b/parts/django/tests/regressiontests/test_client_regress/fixtures/testdata.json
new file mode 100644
index 0000000..0dcf625
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/fixtures/testdata.json
@@ -0,0 +1,56 @@
+[
+ {
+ "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": "2",
+ "model": "auth.user",
+ "fields": {
+ "username": "inactive",
+ "first_name": "Inactive",
+ "last_name": "User",
+ "is_active": false,
+ "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": "3",
+ "model": "auth.user",
+ "fields": {
+ "username": "staff",
+ "first_name": "Staff",
+ "last_name": "Member",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": true,
+ "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"
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/test_client_regress/models.py b/parts/django/tests/regressiontests/test_client_regress/models.py
new file mode 100644
index 0000000..40c7623
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/models.py
@@ -0,0 +1,864 @@
+# -*- coding: utf-8 -*-
+"""
+Regression tests for the Test Client, especially the customized assertions.
+"""
+import os
+
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation
+from django.core.urlresolvers import reverse
+from django.template import (TemplateDoesNotExist, TemplateSyntaxError,
+ Context, loader)
+from django.test import TestCase, Client
+from django.test.client import encode_file
+from django.test.utils import ContextList
+
+
+class AssertContainsTests(TestCase):
+ def setUp(self):
+ self.old_templates = settings.TEMPLATE_DIRS
+ settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),)
+
+ def tearDown(self):
+ settings.TEMPLATE_DIRS = self.old_templates
+
+ def test_contains(self):
+ "Responses can be inspected for content, including counting repeated substrings"
+ response = self.client.get('/test_client_regress/no_template_view/')
+
+ self.assertNotContains(response, 'never')
+ self.assertContains(response, 'never', 0)
+ self.assertContains(response, 'once')
+ self.assertContains(response, 'once', 1)
+ self.assertContains(response, 'twice')
+ self.assertContains(response, 'twice', 2)
+
+ try:
+ self.assertContains(response, 'text', status_code=999)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Couldn't retrieve page: Response code was 200 (expected 999)")
+ try:
+ self.assertContains(response, 'text', status_code=999, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Couldn't retrieve page: Response code was 200 (expected 999)")
+
+ try:
+ self.assertNotContains(response, 'text', status_code=999)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Couldn't retrieve page: Response code was 200 (expected 999)")
+ try:
+ self.assertNotContains(response, 'text', status_code=999, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Couldn't retrieve page: Response code was 200 (expected 999)")
+
+ try:
+ self.assertNotContains(response, 'once')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response should not contain 'once'")
+ try:
+ self.assertNotContains(response, 'once', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Response should not contain 'once'")
+
+ try:
+ self.assertContains(response, 'never', 1)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Found 0 instances of 'never' in response (expected 1)")
+ try:
+ self.assertContains(response, 'never', 1, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Found 0 instances of 'never' in response (expected 1)")
+
+ try:
+ self.assertContains(response, 'once', 0)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 0)")
+ try:
+ self.assertContains(response, 'once', 0, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Found 1 instances of 'once' in response (expected 0)")
+
+ try:
+ self.assertContains(response, 'once', 2)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 2)")
+ try:
+ self.assertContains(response, 'once', 2, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Found 1 instances of 'once' in response (expected 2)")
+
+ try:
+ self.assertContains(response, 'twice', 1)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Found 2 instances of 'twice' in response (expected 1)")
+ try:
+ self.assertContains(response, 'twice', 1, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Found 2 instances of 'twice' in response (expected 1)")
+
+ try:
+ self.assertContains(response, 'thrice')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Couldn't find 'thrice' in response")
+ try:
+ self.assertContains(response, 'thrice', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Couldn't find 'thrice' in response")
+
+ try:
+ self.assertContains(response, 'thrice', 3)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Found 0 instances of 'thrice' in response (expected 3)")
+ try:
+ self.assertContains(response, 'thrice', 3, msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Found 0 instances of 'thrice' in response (expected 3)")
+
+ def test_unicode_contains(self):
+ "Unicode characters can be found in template context"
+ #Regression test for #10183
+ r = self.client.get('/test_client_regress/check_unicode/')
+ self.assertContains(r, u'さかき')
+ self.assertContains(r, '\xe5\xb3\xa0'.decode('utf-8'))
+
+ def test_unicode_not_contains(self):
+ "Unicode characters can be searched for, and not found in template context"
+ #Regression test for #10183
+ r = self.client.get('/test_client_regress/check_unicode/')
+ self.assertNotContains(r, u'はたけ')
+ self.assertNotContains(r, '\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8'))
+
+
+class AssertTemplateUsedTests(TestCase):
+ fixtures = ['testdata.json']
+
+ def test_no_context(self):
+ "Template usage assertions work then templates aren't in use"
+ response = self.client.get('/test_client_regress/no_template_view/')
+
+ # Check that the no template case doesn't mess with the template assertions
+ self.assertTemplateNotUsed(response, 'GET Template')
+
+ try:
+ self.assertTemplateUsed(response, 'GET Template')
+ except AssertionError, e:
+ self.assertEquals(str(e), "No templates used to render the response")
+
+ try:
+ self.assertTemplateUsed(response, 'GET Template', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: No templates used to render the response")
+
+ def test_single_context(self):
+ "Template assertions work when there is a single context"
+ response = self.client.get('/test_client/post_view/', {})
+
+ try:
+ self.assertTemplateNotUsed(response, 'Empty GET Template')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateNotUsed(response, 'Empty GET Template', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Template 'Empty GET Template' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateUsed(response, 'Empty POST Template')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template")
+
+ try:
+ self.assertTemplateUsed(response, 'Empty POST Template', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template")
+
+ def test_multiple_context(self):
+ "Template assertions work when there are multiple contexts"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'foo@example.com',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view_with_template/', post_data)
+ self.assertContains(response, 'POST data OK')
+ try:
+ self.assertTemplateNotUsed(response, "form_view.html")
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'form_view.html' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateNotUsed(response, 'base.html')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateUsed(response, "Valid POST Template")
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html")
+
+class AssertRedirectsTests(TestCase):
+ def test_redirect_page(self):
+ "An assertion is raised if the original page couldn't be retrieved as expected"
+ # This page will redirect with code 301, not 302
+ response = self.client.get('/test_client/permanent_redirect_view/')
+ try:
+ self.assertRedirects(response, '/test_client/get_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
+
+ try:
+ self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 301 (expected 302)")
+
+ def test_lost_query(self):
+ "An assertion is raised if the redirect location doesn't preserve GET parameters"
+ response = self.client.get('/test_client/redirect_view/', {'var': 'value'})
+ try:
+ self.assertRedirects(response, '/test_client/get_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'")
+
+ try:
+ self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'")
+
+ def test_incorrect_target(self):
+ "An assertion is raised if the response redirects to another target"
+ response = self.client.get('/test_client/permanent_redirect_view/')
+ try:
+ # Should redirect to get_view
+ self.assertRedirects(response, '/test_client/some_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
+
+ def test_target_page(self):
+ "An assertion is raised if the response redirect target cannot be retrieved as expected"
+ response = self.client.get('/test_client/double_redirect_view/')
+ try:
+ # The redirect target responds with a 301 code, not 200
+ self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
+
+ try:
+ # The redirect target responds with a 301 code, not 200
+ self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
+
+ def test_redirect_chain(self):
+ "You can follow a redirect chain of multiple redirects"
+ response = self.client.get('/test_client_regress/redirects/further/more/', {}, follow=True)
+ self.assertRedirects(response, '/test_client_regress/no_template_view/',
+ status_code=301, target_status_code=200)
+
+ self.assertEquals(len(response.redirect_chain), 1)
+ self.assertEquals(response.redirect_chain[0], ('http://testserver/test_client_regress/no_template_view/', 301))
+
+ def test_multiple_redirect_chain(self):
+ "You can follow a redirect chain of multiple redirects"
+ response = self.client.get('/test_client_regress/redirects/', {}, follow=True)
+ self.assertRedirects(response, '/test_client_regress/no_template_view/',
+ status_code=301, target_status_code=200)
+
+ self.assertEquals(len(response.redirect_chain), 3)
+ self.assertEquals(response.redirect_chain[0], ('http://testserver/test_client_regress/redirects/further/', 301))
+ self.assertEquals(response.redirect_chain[1], ('http://testserver/test_client_regress/redirects/further/more/', 301))
+ self.assertEquals(response.redirect_chain[2], ('http://testserver/test_client_regress/no_template_view/', 301))
+
+ def test_redirect_chain_to_non_existent(self):
+ "You can follow a chain to a non-existent view"
+ response = self.client.get('/test_client_regress/redirect_to_non_existent_view2/', {}, follow=True)
+ self.assertRedirects(response, '/test_client_regress/non_existent_view/',
+ status_code=301, target_status_code=404)
+
+ def test_redirect_chain_to_self(self):
+ "Redirections to self are caught and escaped"
+ response = self.client.get('/test_client_regress/redirect_to_self/', {}, follow=True)
+ # The chain of redirects stops once the cycle is detected.
+ self.assertRedirects(response, '/test_client_regress/redirect_to_self/',
+ status_code=301, target_status_code=301)
+ self.assertEquals(len(response.redirect_chain), 2)
+
+ def test_circular_redirect(self):
+ "Circular redirect chains are caught and escaped"
+ response = self.client.get('/test_client_regress/circular_redirect_1/', {}, follow=True)
+ # The chain of redirects will get back to the starting point, but stop there.
+ self.assertRedirects(response, '/test_client_regress/circular_redirect_2/',
+ status_code=301, target_status_code=301)
+ self.assertEquals(len(response.redirect_chain), 4)
+
+ def test_redirect_chain_post(self):
+ "A redirect chain will be followed from an initial POST post"
+ response = self.client.post('/test_client_regress/redirects/',
+ {'nothing': 'to_send'}, follow=True)
+ self.assertRedirects(response,
+ '/test_client_regress/no_template_view/', 301, 200)
+ self.assertEquals(len(response.redirect_chain), 3)
+
+ def test_redirect_chain_head(self):
+ "A redirect chain will be followed from an initial HEAD request"
+ response = self.client.head('/test_client_regress/redirects/',
+ {'nothing': 'to_send'}, follow=True)
+ self.assertRedirects(response,
+ '/test_client_regress/no_template_view/', 301, 200)
+ self.assertEquals(len(response.redirect_chain), 3)
+
+ def test_redirect_chain_options(self):
+ "A redirect chain will be followed from an initial OPTIONS request"
+ response = self.client.options('/test_client_regress/redirects/',
+ {'nothing': 'to_send'}, follow=True)
+ self.assertRedirects(response,
+ '/test_client_regress/no_template_view/', 301, 200)
+ self.assertEquals(len(response.redirect_chain), 3)
+
+ def test_redirect_chain_put(self):
+ "A redirect chain will be followed from an initial PUT request"
+ response = self.client.put('/test_client_regress/redirects/',
+ {'nothing': 'to_send'}, follow=True)
+ self.assertRedirects(response,
+ '/test_client_regress/no_template_view/', 301, 200)
+ self.assertEquals(len(response.redirect_chain), 3)
+
+ def test_redirect_chain_delete(self):
+ "A redirect chain will be followed from an initial DELETE request"
+ response = self.client.delete('/test_client_regress/redirects/',
+ {'nothing': 'to_send'}, follow=True)
+ self.assertRedirects(response,
+ '/test_client_regress/no_template_view/', 301, 200)
+ self.assertEquals(len(response.redirect_chain), 3)
+
+ def test_redirect_chain_on_non_redirect_page(self):
+ "An assertion is raised if the original page couldn't be retrieved as expected"
+ # This page will redirect with code 301, not 302
+ response = self.client.get('/test_client/get_view/', follow=True)
+ try:
+ self.assertRedirects(response, '/test_client/get_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)")
+
+ try:
+ self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 200 (expected 302)")
+
+ def test_redirect_on_non_redirect_page(self):
+ "An assertion is raised if the original page couldn't be retrieved as expected"
+ # This page will redirect with code 301, not 302
+ response = self.client.get('/test_client/get_view/')
+ try:
+ self.assertRedirects(response, '/test_client/get_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)")
+
+ try:
+ self.assertRedirects(response, '/test_client/get_view/', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEquals(str(e), "abc: Response didn't redirect as expected: Response code was 200 (expected 302)")
+
+
+class AssertFormErrorTests(TestCase):
+ def test_unknown_form(self):
+ "An assertion is raised if the form name is unknown"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response")
+ try:
+ self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEqual(str(e), "abc: The form 'wrong_form' was not used to render the response")
+
+ def test_unknown_field(self):
+ "An assertion is raised if the field name is unknown"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', 'some_field', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'")
+ try:
+ self.assertFormError(response, 'form', 'some_field', 'Some error.', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEqual(str(e), "abc: The form 'form' in context 0 does not contain the field 'some_field'")
+
+ def test_noerror_field(self):
+ "An assertion is raised if the field doesn't have any errors"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', 'value', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors")
+ try:
+ self.assertFormError(response, 'form', 'value', 'Some error.', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEqual(str(e), "abc: The field 'value' on form 'form' in context 0 contains no errors")
+
+ def test_unknown_error(self):
+ "An assertion is raised if the field doesn't contain the provided error"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', 'email', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+ try:
+ self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEqual(str(e), "abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+
+ def test_unknown_nonfield_error(self):
+ """
+ Checks that an assertion is raised if the form's non field errors
+ doesn't contain the provided error.
+ """
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', None, 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
+ try:
+ self.assertFormError(response, 'form', None, 'Some error.', msg_prefix='abc')
+ except AssertionError, e:
+ self.assertEqual(str(e), "abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
+
+class LoginTests(TestCase):
+ fixtures = ['testdata']
+
+ def test_login_different_client(self):
+ "Check that using a different test client doesn't violate authentication"
+
+ # Create a second client, and log in.
+ c = Client()
+ login = c.login(username='testclient', password='password')
+ self.assertTrue(login, 'Could not log in')
+
+ # Get a redirection page with the second client.
+ response = c.get("/test_client_regress/login_protected_redirect_view/")
+
+ # At this points, the self.client isn't logged in.
+ # Check that assertRedirects uses the original client, not the
+ # default client.
+ self.assertRedirects(response, "http://testserver/test_client_regress/get_view/")
+
+
+class SessionEngineTests(TestCase):
+ fixtures = ['testdata']
+
+ def setUp(self):
+ self.old_SESSION_ENGINE = settings.SESSION_ENGINE
+ settings.SESSION_ENGINE = 'regressiontests.test_client_regress.session'
+
+ def tearDown(self):
+ settings.SESSION_ENGINE = self.old_SESSION_ENGINE
+
+ def test_login(self):
+ "A session engine that modifies the session key can be used to log in"
+ login = self.client.login(username='testclient', password='password')
+ self.assertTrue(login, 'Could not log in')
+
+ # Try to access a login protected page.
+ response = self.client.get("/test_client/login_protected_view/")
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
+class URLEscapingTests(TestCase):
+ def test_simple_argument_get(self):
+ "Get a view that has a simple string argument"
+ response = self.client.get(reverse('arg_view', args=['Slartibartfast']))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'Howdy, Slartibartfast')
+
+ def test_argument_with_space_get(self):
+ "Get a view that has a string argument that requires escaping"
+ response = self.client.get(reverse('arg_view', args=['Arthur Dent']))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'Hi, Arthur')
+
+ def test_simple_argument_post(self):
+ "Post for a view that has a simple string argument"
+ response = self.client.post(reverse('arg_view', args=['Slartibartfast']))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'Howdy, Slartibartfast')
+
+ def test_argument_with_space_post(self):
+ "Post for a view that has a string argument that requires escaping"
+ response = self.client.post(reverse('arg_view', args=['Arthur Dent']))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'Hi, Arthur')
+
+class ExceptionTests(TestCase):
+ fixtures = ['testdata.json']
+
+ def test_exception_cleared(self):
+ "#5836 - A stale user exception isn't re-raised by the test client."
+
+ login = self.client.login(username='testclient',password='password')
+ self.assertTrue(login, 'Could not log in')
+ try:
+ response = self.client.get("/test_client_regress/staff_only/")
+ self.fail("General users should not be able to visit this page")
+ except SuspiciousOperation:
+ pass
+
+ # At this point, an exception has been raised, and should be cleared.
+
+ # This next operation should be successful; if it isn't we have a problem.
+ login = self.client.login(username='staff', password='password')
+ self.assertTrue(login, 'Could not log in')
+ try:
+ self.client.get("/test_client_regress/staff_only/")
+ except SuspiciousOperation:
+ self.fail("Staff should be able to visit this page")
+
+class TemplateExceptionTests(TestCase):
+ def setUp(self):
+ # Reset the loaders so they don't try to render cached templates.
+ if loader.template_source_loaders is not None:
+ for template_loader in loader.template_source_loaders:
+ if hasattr(template_loader, 'reset'):
+ template_loader.reset()
+ self.old_templates = settings.TEMPLATE_DIRS
+ settings.TEMPLATE_DIRS = ()
+
+ def tearDown(self):
+ settings.TEMPLATE_DIRS = self.old_templates
+
+ def test_no_404_template(self):
+ "Missing templates are correctly reported by test client"
+ try:
+ response = self.client.get("/no_such_view/")
+ self.fail("Should get error about missing template")
+ except TemplateDoesNotExist:
+ pass
+
+ def test_bad_404_template(self):
+ "Errors found when rendering 404 error templates are re-raised"
+ settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'bad_templates'),)
+ try:
+ response = self.client.get("/no_such_view/")
+ self.fail("Should get error about syntax error in template")
+ except TemplateSyntaxError:
+ pass
+
+# We need two different tests to check URLconf substitution - one to check
+# it was changed, and another one (without self.urls) to check it was reverted on
+# teardown. This pair of tests relies upon the alphabetical ordering of test execution.
+class UrlconfSubstitutionTests(TestCase):
+ urls = 'regressiontests.test_client_regress.urls'
+
+ def test_urlconf_was_changed(self):
+ "TestCase can enforce a custom URLconf on a per-test basis"
+ url = reverse('arg_view', args=['somename'])
+ self.assertEquals(url, '/arg_view/somename/')
+
+# This test needs to run *after* UrlconfSubstitutionTests; the zz prefix in the
+# name is to ensure alphabetical ordering.
+class zzUrlconfSubstitutionTests(TestCase):
+ def test_urlconf_was_reverted(self):
+ "URLconf is reverted to original value after modification in a TestCase"
+ url = reverse('arg_view', args=['somename'])
+ self.assertEquals(url, '/test_client_regress/arg_view/somename/')
+
+class ContextTests(TestCase):
+ fixtures = ['testdata']
+
+ def test_single_context(self):
+ "Context variables can be retrieved from a single context"
+ response = self.client.get("/test_client_regress/request_data/", data={'foo':'whiz'})
+ self.assertEqual(response.context.__class__, Context)
+ self.assertTrue('get-foo' in response.context)
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['request-foo'], 'whiz')
+ self.assertEqual(response.context['data'], 'sausage')
+
+ try:
+ response.context['does-not-exist']
+ self.fail('Should not be able to retrieve non-existent key')
+ except KeyError, e:
+ self.assertEquals(e.args[0], 'does-not-exist')
+
+ def test_inherited_context(self):
+ "Context variables can be retrieved from a list of contexts"
+ response = self.client.get("/test_client_regress/request_data_extended/", data={'foo':'whiz'})
+ self.assertEqual(response.context.__class__, ContextList)
+ self.assertEqual(len(response.context), 2)
+ self.assertTrue('get-foo' in response.context)
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['request-foo'], 'whiz')
+ self.assertEqual(response.context['data'], 'bacon')
+
+ try:
+ response.context['does-not-exist']
+ self.fail('Should not be able to retrieve non-existent key')
+ except KeyError, e:
+ self.assertEquals(e.args[0], 'does-not-exist')
+
+
+class SessionTests(TestCase):
+ fixtures = ['testdata.json']
+
+ def test_session(self):
+ "The session isn't lost if a user logs in"
+ # The session doesn't exist to start.
+ response = self.client.get('/test_client_regress/check_session/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'NO')
+
+ # This request sets a session variable.
+ response = self.client.get('/test_client_regress/set_session/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'set_session')
+
+ # Check that the session has been modified
+ response = self.client.get('/test_client_regress/check_session/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'YES')
+
+ # Log in
+ login = self.client.login(username='testclient',password='password')
+ self.assertTrue(login, 'Could not log in')
+
+ # Session should still contain the modified value
+ response = self.client.get('/test_client_regress/check_session/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'YES')
+
+ def test_logout(self):
+ """Logout should work whether the user is logged in or not (#9978)."""
+ self.client.logout()
+ login = self.client.login(username='testclient',password='password')
+ self.assertTrue(login, 'Could not log in')
+ self.client.logout()
+ self.client.logout()
+
+class RequestMethodTests(TestCase):
+ def test_get(self):
+ "Request a view via request method GET"
+ response = self.client.get('/test_client_regress/request_methods/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: GET')
+
+ def test_post(self):
+ "Request a view via request method POST"
+ response = self.client.post('/test_client_regress/request_methods/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: POST')
+
+ def test_head(self):
+ "Request a view via request method HEAD"
+ response = self.client.head('/test_client_regress/request_methods/')
+ self.assertEqual(response.status_code, 200)
+ # A HEAD request doesn't return any content.
+ self.assertNotEqual(response.content, 'request method: HEAD')
+ self.assertEqual(response.content, '')
+
+ def test_options(self):
+ "Request a view via request method OPTIONS"
+ response = self.client.options('/test_client_regress/request_methods/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: OPTIONS')
+
+ def test_put(self):
+ "Request a view via request method PUT"
+ response = self.client.put('/test_client_regress/request_methods/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: PUT')
+
+ def test_delete(self):
+ "Request a view via request method DELETE"
+ response = self.client.delete('/test_client_regress/request_methods/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: DELETE')
+
+class RequestMethodStringDataTests(TestCase):
+ def test_post(self):
+ "Request a view with string data via request method POST"
+ # Regression test for #11371
+ data = u'{"test": "json"}'
+ response = self.client.post('/test_client_regress/request_methods/', data=data, content_type='application/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: POST')
+
+ def test_put(self):
+ "Request a view with string data via request method PUT"
+ # Regression test for #11371
+ data = u'{"test": "json"}'
+ response = self.client.put('/test_client_regress/request_methods/', data=data, content_type='application/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'request method: PUT')
+
+class QueryStringTests(TestCase):
+ def test_get_like_requests(self):
+ for method_name in ('get','head','options','put','delete'):
+ # A GET-like request can pass a query string as data
+ method = getattr(self.client, method_name)
+ response = method("/test_client_regress/request_data/", data={'foo':'whiz'})
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['request-foo'], 'whiz')
+
+ # A GET-like request can pass a query string as part of the URL
+ response = method("/test_client_regress/request_data/?foo=whiz")
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['request-foo'], 'whiz')
+
+ # Data provided in the URL to a GET-like request is overridden by actual form data
+ response = method("/test_client_regress/request_data/?foo=whiz", data={'foo':'bang'})
+ self.assertEqual(response.context['get-foo'], 'bang')
+ self.assertEqual(response.context['request-foo'], 'bang')
+
+ response = method("/test_client_regress/request_data/?foo=whiz", data={'bar':'bang'})
+ self.assertEqual(response.context['get-foo'], None)
+ self.assertEqual(response.context['get-bar'], 'bang')
+ self.assertEqual(response.context['request-foo'], None)
+ self.assertEqual(response.context['request-bar'], 'bang')
+
+ def test_post_like_requests(self):
+ # A POST-like request can pass a query string as data
+ response = self.client.post("/test_client_regress/request_data/", data={'foo':'whiz'})
+ self.assertEqual(response.context['get-foo'], None)
+ self.assertEqual(response.context['post-foo'], 'whiz')
+
+ # A POST-like request can pass a query string as part of the URL
+ response = self.client.post("/test_client_regress/request_data/?foo=whiz")
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['post-foo'], None)
+ self.assertEqual(response.context['request-foo'], 'whiz')
+
+ # POST data provided in the URL augments actual form data
+ response = self.client.post("/test_client_regress/request_data/?foo=whiz", data={'foo':'bang'})
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['post-foo'], 'bang')
+ self.assertEqual(response.context['request-foo'], 'bang')
+
+ response = self.client.post("/test_client_regress/request_data/?foo=whiz", data={'bar':'bang'})
+ self.assertEqual(response.context['get-foo'], 'whiz')
+ self.assertEqual(response.context['get-bar'], None)
+ self.assertEqual(response.context['post-foo'], None)
+ self.assertEqual(response.context['post-bar'], 'bang')
+ self.assertEqual(response.context['request-foo'], 'whiz')
+ self.assertEqual(response.context['request-bar'], 'bang')
+
+class UnicodePayloadTests(TestCase):
+ def test_simple_unicode_payload(self):
+ "A simple ASCII-only unicode JSON document can be POSTed"
+ # Regression test for #10571
+ json = u'{"english": "mountain pass"}'
+ response = self.client.post("/test_client_regress/parse_unicode_json/", json,
+ content_type="application/json")
+ self.assertEqual(response.content, json)
+
+ def test_unicode_payload_utf8(self):
+ "A non-ASCII unicode data encoded as UTF-8 can be POSTed"
+ # Regression test for #10571
+ json = u'{"dog": "собака"}'
+ response = self.client.post("/test_client_regress/parse_unicode_json/", json,
+ content_type="application/json; charset=utf-8")
+ self.assertEqual(response.content, json.encode('utf-8'))
+
+ def test_unicode_payload_utf16(self):
+ "A non-ASCII unicode data encoded as UTF-16 can be POSTed"
+ # Regression test for #10571
+ json = u'{"dog": "собака"}'
+ response = self.client.post("/test_client_regress/parse_unicode_json/", json,
+ content_type="application/json; charset=utf-16")
+ self.assertEqual(response.content, json.encode('utf-16'))
+
+ def test_unicode_payload_non_utf(self):
+ "A non-ASCII unicode data as a non-UTF based encoding can be POSTed"
+ #Regression test for #10571
+ json = u'{"dog": "собака"}'
+ response = self.client.post("/test_client_regress/parse_unicode_json/", json,
+ content_type="application/json; charset=koi8-r")
+ self.assertEqual(response.content, json.encode('koi8-r'))
+
+class DummyFile(object):
+ def __init__(self, filename):
+ self.name = filename
+ def read(self):
+ return 'TEST_FILE_CONTENT'
+
+class UploadedFileEncodingTest(TestCase):
+ def test_file_encoding(self):
+ encoded_file = encode_file('TEST_BOUNDARY', 'TEST_KEY', DummyFile('test_name.bin'))
+ self.assertEqual('--TEST_BOUNDARY', encoded_file[0])
+ self.assertEqual('Content-Disposition: form-data; name="TEST_KEY"; filename="test_name.bin"', encoded_file[1])
+ self.assertEqual('TEST_FILE_CONTENT', encoded_file[-1])
+
+ def test_guesses_content_type_on_file_encoding(self):
+ self.assertEqual('Content-Type: application/octet-stream',
+ encode_file('IGNORE', 'IGNORE', DummyFile("file.bin"))[2])
+ self.assertEqual('Content-Type: text/plain',
+ encode_file('IGNORE', 'IGNORE', DummyFile("file.txt"))[2])
+ self.assertEqual('Content-Type: application/zip',
+ encode_file('IGNORE', 'IGNORE', DummyFile("file.zip"))[2])
+ self.assertEqual('Content-Type: application/octet-stream',
+ encode_file('IGNORE', 'IGNORE', DummyFile("file.unknown"))[2])
+
+class RequestHeadersTest(TestCase):
+ def test_client_headers(self):
+ "A test client can receive custom headers"
+ response = self.client.get("/test_client_regress/check_headers/", HTTP_X_ARG_CHECK='Testing 123')
+ self.assertEquals(response.content, "HTTP_X_ARG_CHECK: Testing 123")
+ self.assertEquals(response.status_code, 200)
+
+ def test_client_headers_redirect(self):
+ "Test client headers are preserved through redirects"
+ response = self.client.get("/test_client_regress/check_headers_redirect/", follow=True, HTTP_X_ARG_CHECK='Testing 123')
+ self.assertEquals(response.content, "HTTP_X_ARG_CHECK: Testing 123")
+ self.assertRedirects(response, '/test_client_regress/check_headers/',
+ status_code=301, target_status_code=200)
diff --git a/parts/django/tests/regressiontests/test_client_regress/session.py b/parts/django/tests/regressiontests/test_client_regress/session.py
new file mode 100644
index 0000000..74ae3b6
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/session.py
@@ -0,0 +1,30 @@
+from django.contrib.sessions.backends.base import SessionBase
+
+class SessionStore(SessionBase):
+ """
+ A simple cookie-based session storage implemenation.
+
+ The session key is actually the session data, pickled and encoded.
+ This means that saving the session will change the session key.
+ """
+ def __init__(self, session_key=None):
+ super(SessionStore, self).__init__(session_key)
+
+ def exists(self, session_key):
+ return False
+
+ def create(self):
+ self.session_key = self.encode({})
+
+ def save(self, must_create=False):
+ self.session_key = self.encode(self._session)
+
+ def delete(self, session_key=None):
+ self.session_key = self.encode({})
+
+ def load(self):
+ try:
+ return self.decode(self.session_key)
+ except:
+ self.modified = True
+ return {} \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/test_client_regress/templates/unicode.html b/parts/django/tests/regressiontests/test_client_regress/templates/unicode.html
new file mode 100644
index 0000000..bdb6c91
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/templates/unicode.html
@@ -0,0 +1,5 @@
+* 峠 (とうげ tōge "mountain pass")
+* 榊 (さかき sakaki "tree, genus Cleyera")
+* 辻 (つじ tsuji "crossroads, street")
+* 働 (どう dō, はたら hatara(ku) "work")
+* 腺 (せん sen, "gland")
diff --git a/parts/django/tests/regressiontests/test_client_regress/urls.py b/parts/django/tests/regressiontests/test_client_regress/urls.py
new file mode 100644
index 0000000..650d80b
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/urls.py
@@ -0,0 +1,29 @@
+from django.conf.urls.defaults import *
+from django.views.generic.simple import redirect_to
+import views
+
+urlpatterns = patterns('',
+ (r'^no_template_view/$', views.no_template_view),
+ (r'^staff_only/$', views.staff_only_view),
+ (r'^get_view/$', views.get_view),
+ (r'^request_data/$', views.request_data),
+ (r'^request_data_extended/$', views.request_data, {'template':'extended.html', 'data':'bacon'}),
+ url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
+ (r'^login_protected_redirect_view/$', views.login_protected_redirect_view),
+ (r'^redirects/$', redirect_to, {'url': '/test_client_regress/redirects/further/'}),
+ (r'^redirects/further/$', redirect_to, {'url': '/test_client_regress/redirects/further/more/'}),
+ (r'^redirects/further/more/$', redirect_to, {'url': '/test_client_regress/no_template_view/'}),
+ (r'^redirect_to_non_existent_view/$', redirect_to, {'url': '/test_client_regress/non_existent_view/'}),
+ (r'^redirect_to_non_existent_view2/$', redirect_to, {'url': '/test_client_regress/redirect_to_non_existent_view/'}),
+ (r'^redirect_to_self/$', redirect_to, {'url': '/test_client_regress/redirect_to_self/'}),
+ (r'^circular_redirect_1/$', redirect_to, {'url': '/test_client_regress/circular_redirect_2/'}),
+ (r'^circular_redirect_2/$', redirect_to, {'url': '/test_client_regress/circular_redirect_3/'}),
+ (r'^circular_redirect_3/$', redirect_to, {'url': '/test_client_regress/circular_redirect_1/'}),
+ (r'^set_session/$', views.set_session_view),
+ (r'^check_session/$', views.check_session_view),
+ (r'^request_methods/$', views.request_methods_view),
+ (r'^check_unicode/$', views.return_unicode),
+ (r'^parse_unicode_json/$', views.return_json_file),
+ (r'^check_headers/$', views.check_headers),
+ (r'^check_headers_redirect/$', redirect_to, {'url': '/test_client_regress/check_headers/'}),
+)
diff --git a/parts/django/tests/regressiontests/test_client_regress/views.py b/parts/django/tests/regressiontests/test_client_regress/views.py
new file mode 100644
index 0000000..40aa61f
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_client_regress/views.py
@@ -0,0 +1,93 @@
+from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, HttpResponseRedirect
+from django.core.exceptions import SuspiciousOperation
+from django.shortcuts import render_to_response
+from django.utils import simplejson
+from django.utils.encoding import smart_str
+from django.core.serializers.json import DjangoJSONEncoder
+from django.test.client import CONTENT_TYPE_RE
+
+def no_template_view(request):
+ "A simple view that expects a GET request, and returns a rendered template"
+ return HttpResponse("No template used. Sample content: twice once twice. Content ends.")
+
+def staff_only_view(request):
+ "A view that can only be visited by staff. Non staff members get an exception"
+ if request.user.is_staff:
+ return HttpResponse('')
+ else:
+ raise SuspiciousOperation()
+
+def get_view(request):
+ "A simple login protected view"
+ return HttpResponse("Hello world")
+get_view = login_required(get_view)
+
+def request_data(request, template='base.html', data='sausage'):
+ "A simple view that returns the request data in the context"
+ return render_to_response(template, {
+ 'get-foo':request.GET.get('foo',None),
+ 'get-bar':request.GET.get('bar',None),
+ 'post-foo':request.POST.get('foo',None),
+ 'post-bar':request.POST.get('bar',None),
+ 'request-foo':request.REQUEST.get('foo',None),
+ 'request-bar':request.REQUEST.get('bar',None),
+ 'data': data,
+ })
+
+def view_with_argument(request, name):
+ """A view that takes a string argument
+
+ The purpose of this view is to check that if a space is provided in
+ the argument, the test framework unescapes the %20 before passing
+ the value to the view.
+ """
+ if name == 'Arthur Dent':
+ return HttpResponse('Hi, Arthur')
+ else:
+ return HttpResponse('Howdy, %s' % name)
+
+def login_protected_redirect_view(request):
+ "A view that redirects all requests to the GET view"
+ return HttpResponseRedirect('/test_client_regress/get_view/')
+login_protected_redirect_view = login_required(login_protected_redirect_view)
+
+def set_session_view(request):
+ "A view that sets a session variable"
+ request.session['session_var'] = 'YES'
+ return HttpResponse('set_session')
+
+def check_session_view(request):
+ "A view that reads a session variable"
+ return HttpResponse(request.session.get('session_var', 'NO'))
+
+def request_methods_view(request):
+ "A view that responds with the request method"
+ return HttpResponse('request method: %s' % request.method)
+
+def return_unicode(request):
+ return render_to_response('unicode.html')
+
+def return_json_file(request):
+ "A view that parses and returns a JSON string as a file."
+ match = CONTENT_TYPE_RE.match(request.META['CONTENT_TYPE'])
+ if match:
+ charset = match.group(1)
+ else:
+ charset = settings.DEFAULT_CHARSET
+
+ # This just checks that the uploaded data is JSON
+ obj_dict = simplejson.loads(request.raw_post_data.decode(charset))
+ obj_json = simplejson.dumps(obj_dict, encoding=charset,
+ cls=DjangoJSONEncoder,
+ ensure_ascii=False)
+ response = HttpResponse(smart_str(obj_json, encoding=charset), status=200,
+ mimetype='application/json; charset=' + charset)
+ response['Content-Disposition'] = 'attachment; filename=testfile.json'
+ return response
+
+def check_headers(request):
+ "A view that responds with value of the X-ARG-CHECK header"
+ return HttpResponse('HTTP_X_ARG_CHECK: %s' % request.META.get('HTTP_X_ARG_CHECK', 'Undefined'))
+
diff --git a/parts/django/tests/regressiontests/test_runner/__init__.py b/parts/django/tests/regressiontests/test_runner/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_runner/__init__.py
diff --git a/parts/django/tests/regressiontests/test_runner/models.py b/parts/django/tests/regressiontests/test_runner/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_runner/models.py
diff --git a/parts/django/tests/regressiontests/test_runner/tests.py b/parts/django/tests/regressiontests/test_runner/tests.py
new file mode 100644
index 0000000..3387d65
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_runner/tests.py
@@ -0,0 +1,120 @@
+"""
+Tests for django test runner
+"""
+import StringIO
+import unittest
+import django
+from django.core.exceptions import ImproperlyConfigured
+from django.test import simple
+
+class DjangoTestRunnerTests(unittest.TestCase):
+
+ def test_failfast(self):
+ class MockTestOne(unittest.TestCase):
+ def runTest(self):
+ assert False
+ class MockTestTwo(unittest.TestCase):
+ def runTest(self):
+ assert False
+
+ suite = unittest.TestSuite([MockTestOne(), MockTestTwo()])
+ mock_stream = StringIO.StringIO()
+ dtr = simple.DjangoTestRunner(verbosity=0, failfast=False, stream=mock_stream)
+ result = dtr.run(suite)
+ self.assertEqual(2, result.testsRun)
+ self.assertEqual(2, len(result.failures))
+
+ dtr = simple.DjangoTestRunner(verbosity=0, failfast=True, stream=mock_stream)
+ result = dtr.run(suite)
+ self.assertEqual(1, result.testsRun)
+ self.assertEqual(1, len(result.failures))
+
+class DependencyOrderingTests(unittest.TestCase):
+
+ def test_simple_dependencies(self):
+ raw = [
+ ('s1', ['alpha']),
+ ('s2', ['bravo']),
+ ('s3', ['charlie']),
+ ]
+ dependencies = {
+ 'alpha': ['charlie'],
+ 'bravo': ['charlie'],
+ }
+
+ ordered = simple.dependency_ordered(raw, dependencies=dependencies)
+ ordered_sigs = [sig for sig,aliases in ordered]
+
+ self.assertTrue('s1' in ordered_sigs)
+ self.assertTrue('s2' in ordered_sigs)
+ self.assertTrue('s3' in ordered_sigs)
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s1'))
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s2'))
+
+ def test_chained_dependencies(self):
+ raw = [
+ ('s1', ['alpha']),
+ ('s2', ['bravo']),
+ ('s3', ['charlie']),
+ ]
+ dependencies = {
+ 'alpha': ['bravo'],
+ 'bravo': ['charlie'],
+ }
+
+ ordered = simple.dependency_ordered(raw, dependencies=dependencies)
+ ordered_sigs = [sig for sig,aliases in ordered]
+
+ self.assertTrue('s1' in ordered_sigs)
+ self.assertTrue('s2' in ordered_sigs)
+ self.assertTrue('s3' in ordered_sigs)
+
+ # Explicit dependencies
+ self.assertTrue(ordered_sigs.index('s2') < ordered_sigs.index('s1'))
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s2'))
+
+ # Implied dependencies
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s1'))
+
+ def test_multiple_dependencies(self):
+ raw = [
+ ('s1', ['alpha']),
+ ('s2', ['bravo']),
+ ('s3', ['charlie']),
+ ('s4', ['delta']),
+ ]
+ dependencies = {
+ 'alpha': ['bravo','delta'],
+ 'bravo': ['charlie'],
+ 'delta': ['charlie'],
+ }
+
+ ordered = simple.dependency_ordered(raw, dependencies=dependencies)
+ ordered_sigs = [sig for sig,aliases in ordered]
+
+ self.assertTrue('s1' in ordered_sigs)
+ self.assertTrue('s2' in ordered_sigs)
+ self.assertTrue('s3' in ordered_sigs)
+ self.assertTrue('s4' in ordered_sigs)
+
+ # Explicit dependencies
+ self.assertTrue(ordered_sigs.index('s2') < ordered_sigs.index('s1'))
+ self.assertTrue(ordered_sigs.index('s4') < ordered_sigs.index('s1'))
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s2'))
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s4'))
+
+ # Implicit dependencies
+ self.assertTrue(ordered_sigs.index('s3') < ordered_sigs.index('s1'))
+
+ def test_circular_dependencies(self):
+ raw = [
+ ('s1', ['alpha']),
+ ('s2', ['bravo']),
+ ]
+ dependencies = {
+ 'bravo': ['alpha'],
+ 'alpha': ['bravo'],
+ }
+
+ self.assertRaises(ImproperlyConfigured, simple.dependency_ordered, raw, dependencies=dependencies)
+
diff --git a/parts/django/tests/regressiontests/test_utils/__init__.py b/parts/django/tests/regressiontests/test_utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_utils/__init__.py
diff --git a/parts/django/tests/regressiontests/test_utils/models.py b/parts/django/tests/regressiontests/test_utils/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_utils/models.py
diff --git a/parts/django/tests/regressiontests/test_utils/tests.py b/parts/django/tests/regressiontests/test_utils/tests.py
new file mode 100644
index 0000000..a2539bf
--- /dev/null
+++ b/parts/django/tests/regressiontests/test_utils/tests.py
@@ -0,0 +1,72 @@
+r"""
+# Some checks of the doctest output normalizer.
+# Standard doctests do fairly
+>>> from django.utils import simplejson
+>>> from django.utils.xmlutils import SimplerXMLGenerator
+>>> from StringIO import StringIO
+
+>>> def produce_long():
+... return 42L
+
+>>> def produce_int():
+... return 42
+
+>>> def produce_json():
+... return simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2), 'whiz': 42}])
+
+>>> def produce_xml():
+... stream = StringIO()
+... xml = SimplerXMLGenerator(stream, encoding='utf-8')
+... xml.startDocument()
+... xml.startElement("foo", {"aaa" : "1.0", "bbb": "2.0"})
+... xml.startElement("bar", {"ccc" : "3.0"})
+... xml.characters("Hello")
+... xml.endElement("bar")
+... xml.startElement("whiz", {})
+... xml.characters("Goodbye")
+... xml.endElement("whiz")
+... xml.endElement("foo")
+... xml.endDocument()
+... return stream.getvalue()
+
+>>> def produce_xml_fragment():
+... stream = StringIO()
+... xml = SimplerXMLGenerator(stream, encoding='utf-8')
+... xml.startElement("foo", {"aaa": "1.0", "bbb": "2.0"})
+... xml.characters("Hello")
+... xml.endElement("foo")
+... xml.startElement("bar", {"ccc": "3.0", "ddd": "4.0"})
+... xml.endElement("bar")
+... return stream.getvalue()
+
+# Long values are normalized and are comparable to normal integers ...
+>>> produce_long()
+42
+
+# ... and vice versa
+>>> produce_int()
+42L
+
+# JSON output is normalized for field order, so it doesn't matter
+# which order json dictionary attributes are listed in output
+>>> produce_json()
+'["foo", {"bar": ["baz", null, 1.0, 2], "whiz": 42}]'
+
+>>> produce_json()
+'["foo", {"whiz": 42, "bar": ["baz", null, 1.0, 2]}]'
+
+# XML output is normalized for attribute order, so it doesn't matter
+# which order XML element attributes are listed in output
+>>> produce_xml()
+'<?xml version="1.0" encoding="UTF-8"?>\n<foo aaa="1.0" bbb="2.0"><bar ccc="3.0">Hello</bar><whiz>Goodbye</whiz></foo>'
+
+>>> produce_xml()
+'<?xml version="1.0" encoding="UTF-8"?>\n<foo bbb="2.0" aaa="1.0"><bar ccc="3.0">Hello</bar><whiz>Goodbye</whiz></foo>'
+
+>>> produce_xml_fragment()
+'<foo aaa="1.0" bbb="2.0">Hello</foo><bar ccc="3.0" ddd="4.0"></bar>'
+
+>>> produce_xml_fragment()
+'<foo bbb="2.0" aaa="1.0">Hello</foo><bar ddd="4.0" ccc="3.0"></bar>'
+
+""" \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/text/__init__.py b/parts/django/tests/regressiontests/text/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/text/__init__.py
diff --git a/parts/django/tests/regressiontests/text/models.py b/parts/django/tests/regressiontests/text/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/text/models.py
diff --git a/parts/django/tests/regressiontests/text/tests.py b/parts/django/tests/regressiontests/text/tests.py
new file mode 100644
index 0000000..fd02036
--- /dev/null
+++ b/parts/django/tests/regressiontests/text/tests.py
@@ -0,0 +1,81 @@
+# coding: utf-8
+from django.test import TestCase
+
+from django.utils.text import *
+from django.utils.http import urlquote, urlquote_plus, cookie_date, http_date
+from django.utils.encoding import iri_to_uri
+
+class TextTests(TestCase):
+ """
+ Tests for stuff in django.utils.text and other text munging util functions.
+ """
+
+ def test_smart_split(self):
+
+ self.assertEquals(list(smart_split(r'''This is "a person" test.''')),
+ [u'This', u'is', u'"a person"', u'test.'])
+
+ self.assertEquals(list(smart_split(r'''This is "a person's" test.'''))[2],
+ u'"a person\'s"')
+
+ self.assertEquals(list(smart_split(r'''This is "a person\"s" test.'''))[2],
+ u'"a person\\"s"')
+
+ self.assertEquals(list(smart_split('''"a 'one''')), [u'"a', u"'one"])
+
+ self.assertEquals(list(smart_split(r'''all friends' tests'''))[1],
+ "friends'")
+
+ self.assertEquals(list(smart_split(u'url search_page words="something else"')),
+ [u'url', u'search_page', u'words="something else"'])
+
+ self.assertEquals(list(smart_split(u"url search_page words='something else'")),
+ [u'url', u'search_page', u"words='something else'"])
+
+ self.assertEquals(list(smart_split(u'url search_page words "something else"')),
+ [u'url', u'search_page', u'words', u'"something else"'])
+
+ self.assertEquals(list(smart_split(u'url search_page words-"something else"')),
+ [u'url', u'search_page', u'words-"something else"'])
+
+ self.assertEquals(list(smart_split(u'url search_page words=hello')),
+ [u'url', u'search_page', u'words=hello'])
+
+ self.assertEquals(list(smart_split(u'url search_page words="something else')),
+ [u'url', u'search_page', u'words="something', u'else'])
+
+ self.assertEquals(list(smart_split("cut:','|cut:' '")),
+ [u"cut:','|cut:' '"])
+
+ def test_urlquote(self):
+
+ self.assertEquals(urlquote(u'Paris & Orl\xe9ans'),
+ u'Paris%20%26%20Orl%C3%A9ans')
+ self.assertEquals(urlquote(u'Paris & Orl\xe9ans', safe="&"),
+ u'Paris%20&%20Orl%C3%A9ans')
+ self.assertEquals(urlquote_plus(u'Paris & Orl\xe9ans'),
+ u'Paris+%26+Orl%C3%A9ans')
+ self.assertEquals(urlquote_plus(u'Paris & Orl\xe9ans', safe="&"),
+ u'Paris+&+Orl%C3%A9ans')
+
+ def test_cookie_date(self):
+ t = 1167616461.0
+ self.assertEquals(cookie_date(t), 'Mon, 01-Jan-2007 01:54:21 GMT')
+
+ def test_http_date(self):
+ t = 1167616461.0
+ self.assertEquals(http_date(t), 'Mon, 01 Jan 2007 01:54:21 GMT')
+
+ def test_iri_to_uri(self):
+ self.assertEquals(iri_to_uri(u'red%09ros\xe9#red'),
+ 'red%09ros%C3%A9#red')
+
+ self.assertEquals(iri_to_uri(u'/blog/for/J\xfcrgen M\xfcnster/'),
+ '/blog/for/J%C3%BCrgen%20M%C3%BCnster/')
+
+ self.assertEquals(iri_to_uri(u'locations/%s' % urlquote_plus(u'Paris & Orl\xe9ans')),
+ 'locations/Paris+%26+Orl%C3%A9ans')
+
+ def test_iri_to_uri_idempotent(self):
+ self.assertEquals(iri_to_uri(iri_to_uri(u'red%09ros\xe9#red')),
+ 'red%09ros%C3%A9#red')
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/__init__.py b/parts/django/tests/regressiontests/urlpatterns_reverse/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/__init__.py
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/extra_urls.py b/parts/django/tests/regressiontests/urlpatterns_reverse/extra_urls.py
new file mode 100644
index 0000000..c171f6d
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/extra_urls.py
@@ -0,0 +1,13 @@
+"""
+Some extra URL patterns that are included at the top level.
+"""
+
+from django.conf.urls.defaults import *
+from views import empty_view
+
+urlpatterns = patterns('',
+ url(r'^e-places/(\d+)/$', empty_view, name='extra-places'),
+ url(r'^e-people/(?P<name>\w+)/$', empty_view, name="extra-people"),
+ url('', include('regressiontests.urlpatterns_reverse.included_urls2')),
+ url(r'^prefix/(?P<prefix>\w+)/', include('regressiontests.urlpatterns_reverse.included_urls2')),
+)
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py b/parts/django/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py
new file mode 100644
index 0000000..0731906
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py
@@ -0,0 +1,13 @@
+from django.conf.urls.defaults import *
+from namespace_urls import URLObject
+
+testobj3 = URLObject('testapp', 'test-ns3')
+
+urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
+ url(r'^normal/$', 'empty_view', name='inc-normal-view'),
+ url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-normal-view'),
+
+ (r'^test3/', include(testobj3.urls)),
+ (r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')),
+)
+
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/included_urls.py b/parts/django/tests/regressiontests/urlpatterns_reverse/included_urls.py
new file mode 100644
index 0000000..f8acf34
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/included_urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+from views import empty_view
+
+urlpatterns = patterns('',
+ url(r'^$', empty_view, name="inner-nothing"),
+ url(r'^extra/(?P<extra>\w+)/$', empty_view, name="inner-extra"),
+ url(r'^(?P<one>\d+)|(?P<two>\d+)/$', empty_view, name="inner-disjunction"),
+)
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/included_urls2.py b/parts/django/tests/regressiontests/urlpatterns_reverse/included_urls2.py
new file mode 100644
index 0000000..f414ca6
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/included_urls2.py
@@ -0,0 +1,14 @@
+"""
+These URL patterns are included in two different ways in the main urls.py, with
+an extra argument present in one case. Thus, there are two different ways for
+each name to resolve and Django must distinguish the possibilities based on the
+argument list.
+"""
+
+from django.conf.urls.defaults import *
+from views import empty_view
+
+urlpatterns = patterns('',
+ url(r'^part/(?P<value>\w+)/$', empty_view, name="part"),
+ url(r'^part2/(?:(?P<value>\w+)/)?$', empty_view, name="part2"),
+)
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/middleware.py b/parts/django/tests/regressiontests/urlpatterns_reverse/middleware.py
new file mode 100644
index 0000000..cd3c045
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/middleware.py
@@ -0,0 +1,11 @@
+from django.core.urlresolvers import set_urlconf
+
+import urlconf_inner
+
+class ChangeURLconfMiddleware(object):
+ def process_request(self, request):
+ request.urlconf = urlconf_inner.__name__
+
+class NullChangeURLconfMiddleware(object):
+ def process_request(self, request):
+ request.urlconf = None
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/models.py b/parts/django/tests/regressiontests/urlpatterns_reverse/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/models.py
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/parts/django/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
new file mode 100644
index 0000000..27cc7f7
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
@@ -0,0 +1,38 @@
+from django.conf.urls.defaults import *
+
+class URLObject(object):
+ def __init__(self, app_name, namespace):
+ self.app_name = app_name
+ self.namespace = namespace
+
+ def urls(self):
+ return patterns('',
+ url(r'^inner/$', 'empty_view', name='urlobject-view'),
+ url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'),
+ ), self.app_name, self.namespace
+ urls = property(urls)
+
+testobj1 = URLObject('testapp', 'test-ns1')
+testobj2 = URLObject('testapp', 'test-ns2')
+default_testobj = URLObject('testapp', 'testapp')
+
+otherobj1 = URLObject('nodefault', 'other-ns1')
+otherobj2 = URLObject('nodefault', 'other-ns2')
+
+urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
+ url(r'^normal/$', 'empty_view', name='normal-view'),
+ url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'),
+
+ (r'^test1/', include(testobj1.urls)),
+ (r'^test2/', include(testobj2.urls)),
+ (r'^default/', include(default_testobj.urls)),
+
+ (r'^other1/', include(otherobj1.urls)),
+ (r'^other2/', include(otherobj2.urls)),
+
+ (r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
+ (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
+
+ (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
+
+)
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/no_urls.py b/parts/django/tests/regressiontests/urlpatterns_reverse/no_urls.py
new file mode 100644
index 0000000..c9b9efe
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/no_urls.py
@@ -0,0 +1,2 @@
+#from django.conf.urls.defaults import *
+
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/tests.py b/parts/django/tests/regressiontests/urlpatterns_reverse/tests.py
new file mode 100644
index 0000000..d0b7146
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/tests.py
@@ -0,0 +1,330 @@
+"""
+Unit tests for reverse URL lookups.
+"""
+import unittest
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404, RegexURLResolver
+from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
+from django.shortcuts import redirect
+from django.test import TestCase
+
+import urlconf_outer
+import urlconf_inner
+import middleware
+
+test_data = (
+ ('places', '/places/3/', [3], {}),
+ ('places', '/places/3/', ['3'], {}),
+ ('places', NoReverseMatch, ['a'], {}),
+ ('places', NoReverseMatch, [], {}),
+ ('places?', '/place/', [], {}),
+ ('places+', '/places/', [], {}),
+ ('places*', '/place/', [], {}),
+ ('places2?', '/', [], {}),
+ ('places2+', '/places/', [], {}),
+ ('places2*', '/', [], {}),
+ ('places3', '/places/4/', [4], {}),
+ ('places3', '/places/harlem/', ['harlem'], {}),
+ ('places3', NoReverseMatch, ['harlem64'], {}),
+ ('places4', '/places/3/', [], {'id': 3}),
+ ('people', NoReverseMatch, [], {}),
+ ('people', '/people/adrian/', ['adrian'], {}),
+ ('people', '/people/adrian/', [], {'name': 'adrian'}),
+ ('people', NoReverseMatch, ['name with spaces'], {}),
+ ('people', NoReverseMatch, [], {'name': 'name with spaces'}),
+ ('people2', '/people/name/', [], {}),
+ ('people2a', '/people/name/fred/', ['fred'], {}),
+ ('optional', '/optional/fred/', [], {'name': 'fred'}),
+ ('optional', '/optional/fred/', ['fred'], {}),
+ ('hardcoded', '/hardcoded/', [], {}),
+ ('hardcoded2', '/hardcoded/doc.pdf', [], {}),
+ ('people3', '/people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}),
+ ('people3', NoReverseMatch, [], {'state': 'il'}),
+ ('people3', NoReverseMatch, [], {'name': 'adrian'}),
+ ('people4', NoReverseMatch, [], {'state': 'il', 'name': 'adrian'}),
+ ('people6', '/people/il/test/adrian/', ['il/test', 'adrian'], {}),
+ ('people6', '/people//adrian/', ['adrian'], {}),
+ ('range', '/character_set/a/', [], {}),
+ ('range2', '/character_set/x/', [], {}),
+ ('price', '/price/$10/', ['10'], {}),
+ ('price2', '/price/$10/', ['10'], {}),
+ ('price3', '/price/$10/', ['10'], {}),
+ ('product', '/product/chocolate+($2.00)/', [], {'price': '2.00', 'product': 'chocolate'}),
+ ('headlines', '/headlines/2007.5.21/', [], dict(year=2007, month=5, day=21)),
+ ('windows', r'/windows_path/C:%5CDocuments%20and%20Settings%5Cspam/', [], dict(drive_name='C', path=r'Documents and Settings\spam')),
+ ('special', r'/special_chars/+%5C$*/', [r'+\$*'], {}),
+ ('special', NoReverseMatch, [''], {}),
+ ('mixed', '/john/0/', [], {'name': 'john'}),
+ ('repeats', '/repeats/a/', [], {}),
+ ('repeats2', '/repeats/aa/', [], {}),
+ ('repeats3', '/repeats/aa/', [], {}),
+ ('insensitive', '/CaseInsensitive/fred', ['fred'], {}),
+ ('test', '/test/1', [], {}),
+ ('test2', '/test/2', [], {}),
+ ('inner-nothing', '/outer/42/', [], {'outer': '42'}),
+ ('inner-nothing', '/outer/42/', ['42'], {}),
+ ('inner-nothing', NoReverseMatch, ['foo'], {}),
+ ('inner-extra', '/outer/42/extra/inner/', [], {'extra': 'inner', 'outer': '42'}),
+ ('inner-extra', '/outer/42/extra/inner/', ['42', 'inner'], {}),
+ ('inner-extra', NoReverseMatch, ['fred', 'inner'], {}),
+ ('disjunction', NoReverseMatch, ['foo'], {}),
+ ('inner-disjunction', NoReverseMatch, ['10', '11'], {}),
+ ('extra-places', '/e-places/10/', ['10'], {}),
+ ('extra-people', '/e-people/fred/', ['fred'], {}),
+ ('extra-people', '/e-people/fred/', [], {'name': 'fred'}),
+ ('part', '/part/one/', [], {'value': 'one'}),
+ ('part', '/prefix/xx/part/one/', [], {'value': 'one', 'prefix': 'xx'}),
+ ('part2', '/part2/one/', [], {'value': 'one'}),
+ ('part2', '/part2/', [], {}),
+ ('part2', '/prefix/xx/part2/one/', [], {'value': 'one', 'prefix': 'xx'}),
+ ('part2', '/prefix/xx/part2/', [], {'prefix': 'xx'}),
+
+ # Regression for #9038
+ # These views are resolved by method name. Each method is deployed twice -
+ # once with an explicit argument, and once using the default value on
+ # the method. This is potentially ambiguous, as you have to pick the
+ # correct view for the arguments provided.
+ ('kwargs_view', '/arg_view/', [], {}),
+ ('kwargs_view', '/arg_view/10/', [], {'arg1':10}),
+ ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}),
+ ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}),
+ ('non_path_include', '/includes/non_path_include/', [], {})
+
+)
+
+class NoURLPatternsTests(TestCase):
+ urls = 'regressiontests.urlpatterns_reverse.no_urls'
+
+ def assertRaisesErrorWithMessage(self, error, message, callable,
+ *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ def test_no_urls_exception(self):
+ """
+ RegexURLResolver should raise an exception when no urlpatterns exist.
+ """
+ resolver = RegexURLResolver(r'^$', self.urls)
+
+ self.assertRaisesErrorWithMessage(ImproperlyConfigured,
+ "The included urlconf regressiontests.urlpatterns_reverse.no_urls "\
+ "doesn't have any patterns in it", getattr, resolver, 'url_patterns')
+
+class URLPatternReverse(TestCase):
+ urls = 'regressiontests.urlpatterns_reverse.urls'
+
+ def test_urlpattern_reverse(self):
+ for name, expected, args, kwargs in test_data:
+ try:
+ got = reverse(name, args=args, kwargs=kwargs)
+ except NoReverseMatch, e:
+ self.assertEqual(expected, NoReverseMatch)
+ else:
+ self.assertEquals(got, expected)
+
+ def test_reverse_none(self):
+ # Reversing None should raise an error, not return the last un-named view.
+ self.assertRaises(NoReverseMatch, reverse, None)
+
+class ResolverTests(unittest.TestCase):
+ def test_non_regex(self):
+ """
+ Verifies that we raise a Resolver404 if what we are resolving doesn't
+ meet the basic requirements of a path to match - i.e., at the very
+ least, it matches the root pattern '^/'. We must never return None
+ from resolve, or we will get a TypeError further down the line.
+
+ Regression for #10834.
+ """
+ self.assertRaises(Resolver404, resolve, '')
+ self.assertRaises(Resolver404, resolve, 'a')
+ self.assertRaises(Resolver404, resolve, '\\')
+ self.assertRaises(Resolver404, resolve, '.')
+
+class ReverseShortcutTests(TestCase):
+ urls = 'regressiontests.urlpatterns_reverse.urls'
+
+ def test_redirect_to_object(self):
+ # We don't really need a model; just something with a get_absolute_url
+ class FakeObj(object):
+ def get_absolute_url(self):
+ return "/hi-there/"
+
+ res = redirect(FakeObj())
+ self.assert_(isinstance(res, HttpResponseRedirect))
+ self.assertEqual(res['Location'], '/hi-there/')
+
+ res = redirect(FakeObj(), permanent=True)
+ self.assert_(isinstance(res, HttpResponsePermanentRedirect))
+ self.assertEqual(res['Location'], '/hi-there/')
+
+ def test_redirect_to_view_name(self):
+ res = redirect('hardcoded2')
+ self.assertEqual(res['Location'], '/hardcoded/doc.pdf')
+ res = redirect('places', 1)
+ self.assertEqual(res['Location'], '/places/1/')
+ res = redirect('headlines', year='2008', month='02', day='17')
+ self.assertEqual(res['Location'], '/headlines/2008.02.17/')
+ self.assertRaises(NoReverseMatch, redirect, 'not-a-view')
+
+ def test_redirect_to_url(self):
+ res = redirect('/foo/')
+ self.assertEqual(res['Location'], '/foo/')
+ res = redirect('http://example.com/')
+ self.assertEqual(res['Location'], 'http://example.com/')
+
+ def test_redirect_view_object(self):
+ from views import absolute_kwargs_view
+ res = redirect(absolute_kwargs_view)
+ self.assertEqual(res['Location'], '/absolute_arg_view/')
+ self.assertRaises(NoReverseMatch, redirect, absolute_kwargs_view, wrong_argument=None)
+
+
+class NamespaceTests(TestCase):
+ urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
+
+ def test_ambiguous_object(self):
+ "Names deployed via dynamic URL objects that require namespaces can't be resolved"
+ self.assertRaises(NoReverseMatch, reverse, 'urlobject-view')
+ self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42])
+ self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37})
+
+ def test_ambiguous_urlpattern(self):
+ "Names deployed via dynamic URL objects that require namespaces can't be resolved"
+ self.assertRaises(NoReverseMatch, reverse, 'inner-nothing')
+ self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42])
+ self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37})
+
+ def test_non_existent_namespace(self):
+ "Non-existent namespaces raise errors"
+ self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view')
+ self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view')
+
+ def test_normal_name(self):
+ "Normal lookups work as expected"
+ self.assertEquals('/normal/', reverse('normal-view'))
+ self.assertEquals('/normal/37/42/', reverse('normal-view', args=[37,42]))
+ self.assertEquals('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_simple_included_name(self):
+ "Normal lookups work on names included from other patterns"
+ self.assertEquals('/included/normal/', reverse('inc-normal-view'))
+ self.assertEquals('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42]))
+ self.assertEquals('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_namespace_object(self):
+ "Dynamic URL objects can be found using a namespace"
+ self.assertEquals('/test1/inner/', reverse('test-ns1:urlobject-view'))
+ self.assertEquals('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42]))
+ self.assertEquals('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_embedded_namespace_object(self):
+ "Namespaces can be installed anywhere in the URL pattern tree"
+ self.assertEquals('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
+ self.assertEquals('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42]))
+ self.assertEquals('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_namespace_pattern(self):
+ "Namespaces can be applied to include()'d urlpatterns"
+ self.assertEquals('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
+ self.assertEquals('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
+ self.assertEquals('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_multiple_namespace_pattern(self):
+ "Namespaces can be embedded"
+ self.assertEquals('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
+ self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42]))
+ self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_app_lookup_object(self):
+ "A default application namespace can be used for lookup"
+ self.assertEquals('/default/inner/', reverse('testapp:urlobject-view'))
+ self.assertEquals('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42]))
+ self.assertEquals('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
+
+ def test_app_lookup_object_with_default(self):
+ "A default application namespace is sensitive to the 'current' app can be used for lookup"
+ self.assertEquals('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3'))
+ self.assertEquals('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], current_app='test-ns3'))
+ self.assertEquals('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='test-ns3'))
+
+ def test_app_lookup_object_without_default(self):
+ "An application namespace without a default is sensitive to the 'current' app can be used for lookup"
+ self.assertEquals('/other2/inner/', reverse('nodefault:urlobject-view'))
+ self.assertEquals('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42]))
+ self.assertEquals('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
+
+ self.assertEquals('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1'))
+ self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
+ self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
+
+class RequestURLconfTests(TestCase):
+ def setUp(self):
+ self.root_urlconf = settings.ROOT_URLCONF
+ self.middleware_classes = settings.MIDDLEWARE_CLASSES
+ settings.ROOT_URLCONF = urlconf_outer.__name__
+
+ def tearDown(self):
+ settings.ROOT_URLCONF = self.root_urlconf
+ settings.MIDDLEWARE_CLASSES = self.middleware_classes
+
+ def test_urlconf(self):
+ response = self.client.get('/test/me/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'outer:/test/me/,'
+ 'inner:/inner_urlconf/second_test/')
+ response = self.client.get('/inner_urlconf/second_test/')
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get('/second_test/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_urlconf_overridden(self):
+ settings.MIDDLEWARE_CLASSES += (
+ '%s.ChangeURLconfMiddleware' % middleware.__name__,
+ )
+ response = self.client.get('/test/me/')
+ self.assertEqual(response.status_code, 404)
+ response = self.client.get('/inner_urlconf/second_test/')
+ self.assertEqual(response.status_code, 404)
+ response = self.client.get('/second_test/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'outer:,inner:/second_test/')
+
+ def test_urlconf_overridden_with_null(self):
+ settings.MIDDLEWARE_CLASSES += (
+ '%s.NullChangeURLconfMiddleware' % middleware.__name__,
+ )
+ self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/')
+
+class ErrorHandlerResolutionTests(TestCase):
+ """Tests for handler404 and handler500"""
+
+ def setUp(self):
+ urlconf = 'regressiontests.urlpatterns_reverse.urls_error_handlers'
+ urlconf_callables = 'regressiontests.urlpatterns_reverse.urls_error_handlers_callables'
+ self.resolver = RegexURLResolver(r'^$', urlconf)
+ self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables)
+
+ def test_named_handlers(self):
+ from views import empty_view
+ handler = (empty_view, {})
+ self.assertEqual(self.resolver.resolve404(), handler)
+ self.assertEqual(self.resolver.resolve500(), handler)
+
+ def test_callable_handers(self):
+ from views import empty_view
+ handler = (empty_view, {})
+ self.assertEqual(self.callable_resolver.resolve404(), handler)
+ self.assertEqual(self.callable_resolver.resolve500(), handler)
+
+class NoRootUrlConfTests(TestCase):
+ """Tests for handler404 and handler500 if urlconf is None"""
+ urls = None
+
+ def test_no_handler_exception(self):
+ self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/')
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py b/parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
new file mode 100644
index 0000000..d188e06
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import *
+from django.template import Template, Context
+from django.http import HttpResponse
+
+def inner_view(request):
+ content = Template('{% url outer as outer_url %}outer:{{ outer_url }},'
+ '{% url inner as inner_url %}inner:{{ inner_url }}').render(Context())
+ return HttpResponse(content)
+
+urlpatterns = patterns('',
+ url(r'^second_test/$', inner_view, name='inner'),
+) \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py b/parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py
new file mode 100644
index 0000000..506e036
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+
+import urlconf_inner
+
+
+urlpatterns = patterns('',
+ url(r'^test/me/$', urlconf_inner.inner_view, name='outer'),
+ url(r'^inner_urlconf/', include(urlconf_inner.__name__))
+) \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/urls.py b/parts/django/tests/regressiontests/urlpatterns_reverse/urls.py
new file mode 100644
index 0000000..c603c02
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/urls.py
@@ -0,0 +1,63 @@
+from django.conf.urls.defaults import *
+from views import empty_view, absolute_kwargs_view
+
+other_patterns = patterns('',
+ url(r'non_path_include/$', empty_view, name='non_path_include'),
+)
+
+urlpatterns = patterns('',
+ url(r'^places/(\d+)/$', empty_view, name='places'),
+ url(r'^places?/$', empty_view, name="places?"),
+ url(r'^places+/$', empty_view, name="places+"),
+ url(r'^places*/$', empty_view, name="places*"),
+ url(r'^(?:places/)?$', empty_view, name="places2?"),
+ url(r'^(?:places/)+$', empty_view, name="places2+"),
+ url(r'^(?:places/)*$', empty_view, name="places2*"),
+ url(r'^places/(\d+|[a-z_]+)/', empty_view, name="places3"),
+ url(r'^places/(?P<id>\d+)/$', empty_view, name="places4"),
+ url(r'^people/(?P<name>\w+)/$', empty_view, name="people"),
+ url(r'^people/(?:name/)', empty_view, name="people2"),
+ url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"),
+ url(r'^optional/(?P<name>.*)/(?:.+/)?', empty_view, name="optional"),
+ url(r'^hardcoded/$', 'hardcoded/', empty_view, name="hardcoded"),
+ url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"),
+ url(r'^people/(?P<state>\w\w)/(?P<name>\w+)/$', empty_view, name="people3"),
+ url(r'^people/(?P<state>\w\w)/(?P<name>\d)/$', empty_view, name="people4"),
+ url(r'^people/((?P<state>\w\w)/test)?/(\w+)/$', empty_view, name="people6"),
+ url(r'^character_set/[abcdef0-9]/$', empty_view, name="range"),
+ url(r'^character_set/[\w]/$', empty_view, name="range2"),
+ url(r'^price/\$(\d+)/$', empty_view, name="price"),
+ url(r'^price/[$](\d+)/$', empty_view, name="price2"),
+ url(r'^price/[\$](\d+)/$', empty_view, name="price3"),
+ url(r'^product/(?P<product>\w+)\+\(\$(?P<price>\d+(\.\d+)?)\)/$',
+ empty_view, name="product"),
+ url(r'^headlines/(?P<year>\d+)\.(?P<month>\d+)\.(?P<day>\d+)/$', empty_view,
+ name="headlines"),
+ url(r'^windows_path/(?P<drive_name>[A-Z]):\\(?P<path>.+)/$', empty_view,
+ name="windows"),
+ url(r'^special_chars/(.+)/$', empty_view, name="special"),
+ url(r'^(?P<name>.+)/\d+/$', empty_view, name="mixed"),
+ url(r'^repeats/a{1,2}/$', empty_view, name="repeats"),
+ url(r'^repeats/a{2,4}/$', empty_view, name="repeats2"),
+ url(r'^repeats/a{2}/$', empty_view, name="repeats3"),
+ url(r'^(?i)CaseInsensitive/(\w+)', empty_view, name="insensitive"),
+ url(r'^test/1/?', empty_view, name="test"),
+ url(r'^(?i)test/2/?$', empty_view, name="test2"),
+ url(r'^outer/(?P<outer>\d+)/',
+ include('regressiontests.urlpatterns_reverse.included_urls')),
+ url('', include('regressiontests.urlpatterns_reverse.extra_urls')),
+
+ # This is non-reversible, but we shouldn't blow up when parsing it.
+ url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"),
+
+ # Regression views for #9038. See tests for more details
+ url(r'arg_view/$', 'kwargs_view'),
+ url(r'arg_view/(?P<arg1>\d+)/$', 'kwargs_view'),
+ url(r'absolute_arg_view/(?P<arg1>\d+)/$', absolute_kwargs_view),
+ url(r'absolute_arg_view/$', absolute_kwargs_view),
+
+ url('^includes/', include(other_patterns)),
+
+)
+
+
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py b/parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py
new file mode 100644
index 0000000..c2e0d32
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py
@@ -0,0 +1,8 @@
+# Used by the ErrorHandlerResolutionTests test case.
+
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('')
+
+handler404 = 'regressiontests.urlpatterns_reverse.views.empty_view'
+handler500 = 'regressiontests.urlpatterns_reverse.views.empty_view'
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py b/parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py
new file mode 100644
index 0000000..00f25a7
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py
@@ -0,0 +1,9 @@
+# Used by the ErrorHandlerResolutionTests test case.
+
+from django.conf.urls.defaults import patterns
+from views import empty_view
+
+urlpatterns = patterns('')
+
+handler404 = empty_view
+handler500 = empty_view
diff --git a/parts/django/tests/regressiontests/urlpatterns_reverse/views.py b/parts/django/tests/regressiontests/urlpatterns_reverse/views.py
new file mode 100644
index 0000000..99c00bd
--- /dev/null
+++ b/parts/django/tests/regressiontests/urlpatterns_reverse/views.py
@@ -0,0 +1,8 @@
+def empty_view(request, *args, **kwargs):
+ pass
+
+def kwargs_view(request, arg1=1, arg2=2):
+ pass
+
+def absolute_kwargs_view(request, arg1=1, arg2=2):
+ pass
diff --git a/parts/django/tests/regressiontests/utils/__init__.py b/parts/django/tests/regressiontests/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/__init__.py
diff --git a/parts/django/tests/regressiontests/utils/checksums.py b/parts/django/tests/regressiontests/utils/checksums.py
new file mode 100644
index 0000000..cee6dca
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/checksums.py
@@ -0,0 +1,29 @@
+import unittest
+
+from django.utils import checksums
+
+class TestUtilsChecksums(unittest.TestCase):
+
+ def check_output(self, function, value, output=None):
+ """
+ Check that function(value) equals output. If output is None,
+ check that function(value) equals value.
+ """
+ if output is None:
+ output = value
+ self.assertEqual(function(value), output)
+
+ def test_luhn(self):
+ f = checksums.luhn
+ items = (
+ (4111111111111111, True), ('4111111111111111', True),
+ (4222222222222, True), (378734493671000, True),
+ (5424000000000015, True), (5555555555554444, True),
+ (1008, True), ('0000001008', True), ('000000001008', True),
+ (4012888888881881, True), (1234567890123456789012345678909, True),
+ (4111111111211111, False), (42222222222224, False),
+ (100, False), ('100', False), ('0000100', False),
+ ('abc', False), (None, False), (object(), False),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
diff --git a/parts/django/tests/regressiontests/utils/datastructures.py b/parts/django/tests/regressiontests/utils/datastructures.py
new file mode 100644
index 0000000..a41281c
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/datastructures.py
@@ -0,0 +1,256 @@
+"""
+Tests for stuff in django.utils.datastructures.
+"""
+import pickle
+import unittest
+
+from django.utils.datastructures import *
+
+
+class DatastructuresTestCase(unittest.TestCase):
+ def assertRaisesErrorWithMessage(self, error, message, callable,
+ *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+
+class SortedDictTests(DatastructuresTestCase):
+ def setUp(self):
+ self.d1 = SortedDict()
+ self.d1[7] = 'seven'
+ self.d1[1] = 'one'
+ self.d1[9] = 'nine'
+
+ self.d2 = SortedDict()
+ self.d2[1] = 'one'
+ self.d2[9] = 'nine'
+ self.d2[0] = 'nil'
+ self.d2[7] = 'seven'
+
+ def test_basic_methods(self):
+ self.assertEquals(self.d1.keys(), [7, 1, 9])
+ self.assertEquals(self.d1.values(), ['seven', 'one', 'nine'])
+ self.assertEquals(self.d1.items(), [(7, 'seven'), (1, 'one'), (9, 'nine')])
+
+ def test_overwrite_ordering(self):
+ """ Overwriting an item keeps it's place. """
+ self.d1[1] = 'ONE'
+ self.assertEquals(self.d1.values(), ['seven', 'ONE', 'nine'])
+
+ def test_append_items(self):
+ """ New items go to the end. """
+ self.d1[0] = 'nil'
+ self.assertEquals(self.d1.keys(), [7, 1, 9, 0])
+
+ def test_delete_and_insert(self):
+ """
+ Deleting an item, then inserting the same key again will place it
+ at the end.
+ """
+ del self.d2[7]
+ self.assertEquals(self.d2.keys(), [1, 9, 0])
+ self.d2[7] = 'lucky number 7'
+ self.assertEquals(self.d2.keys(), [1, 9, 0, 7])
+
+ def test_change_keys(self):
+ """
+ Changing the keys won't do anything, it's only a copy of the
+ keys dict.
+ """
+ k = self.d2.keys()
+ k.remove(9)
+ self.assertEquals(self.d2.keys(), [1, 9, 0, 7])
+
+ def test_init_keys(self):
+ """
+ Initialising a SortedDict with two keys will just take the first one.
+
+ A real dict will actually take the second value so we will too, but
+ we'll keep the ordering from the first key found.
+ """
+ tuples = ((2, 'two'), (1, 'one'), (2, 'second-two'))
+ d = SortedDict(tuples)
+
+ self.assertEquals(d.keys(), [2, 1])
+
+ real_dict = dict(tuples)
+ self.assertEquals(sorted(real_dict.values()), ['one', 'second-two'])
+
+ # Here the order of SortedDict values *is* what we are testing
+ self.assertEquals(d.values(), ['second-two', 'one'])
+
+ def test_overwrite(self):
+ self.d1[1] = 'not one'
+ self.assertEqual(self.d1[1], 'not one')
+ self.assertEqual(self.d1.keys(), self.d1.copy().keys())
+
+ def test_append(self):
+ self.d1[13] = 'thirteen'
+ self.assertEquals(
+ repr(self.d1),
+ "{7: 'seven', 1: 'one', 9: 'nine', 13: 'thirteen'}"
+ )
+
+ def test_pop(self):
+ self.assertEquals(self.d1.pop(1, 'missing'), 'one')
+ self.assertEquals(self.d1.pop(1, 'missing'), 'missing')
+
+ # We don't know which item will be popped in popitem(), so we'll
+ # just check that the number of keys has decreased.
+ l = len(self.d1)
+ self.d1.popitem()
+ self.assertEquals(l - len(self.d1), 1)
+
+ def test_dict_equality(self):
+ d = SortedDict((i, i) for i in xrange(3))
+ self.assertEquals(d, {0: 0, 1: 1, 2: 2})
+
+ def test_tuple_init(self):
+ d = SortedDict(((1, "one"), (0, "zero"), (2, "two")))
+ self.assertEquals(repr(d), "{1: 'one', 0: 'zero', 2: 'two'}")
+
+ def test_pickle(self):
+ self.assertEquals(
+ pickle.loads(pickle.dumps(self.d1, 2)),
+ {7: 'seven', 1: 'one', 9: 'nine'}
+ )
+
+ def test_clear(self):
+ self.d1.clear()
+ self.assertEquals(self.d1, {})
+ self.assertEquals(self.d1.keyOrder, [])
+
+class MergeDictTests(DatastructuresTestCase):
+
+ def test_simple_mergedict(self):
+ d1 = {'chris':'cool', 'camri':'cute', 'cotton':'adorable',
+ 'tulip':'snuggable', 'twoofme':'firstone'}
+
+ d2 = {'chris2':'cool2', 'camri2':'cute2', 'cotton2':'adorable2',
+ 'tulip2':'snuggable2'}
+
+ d3 = {'chris3':'cool3', 'camri3':'cute3', 'cotton3':'adorable3',
+ 'tulip3':'snuggable3'}
+
+ d4 = {'twoofme': 'secondone'}
+
+ md = MergeDict(d1, d2, d3)
+
+ self.assertEquals(md['chris'], 'cool')
+ self.assertEquals(md['camri'], 'cute')
+ self.assertEquals(md['twoofme'], 'firstone')
+
+ md2 = md.copy()
+ self.assertEquals(md2['chris'], 'cool')
+
+ def test_mergedict_merges_multivaluedict(self):
+ """ MergeDict can merge MultiValueDicts """
+
+ multi1 = MultiValueDict({'key1': ['value1'],
+ 'key2': ['value2', 'value3']})
+
+ multi2 = MultiValueDict({'key2': ['value4'],
+ 'key4': ['value5', 'value6']})
+
+ mm = MergeDict(multi1, multi2)
+
+ # Although 'key2' appears in both dictionaries,
+ # only the first value is used.
+ self.assertEquals(mm.getlist('key2'), ['value2', 'value3'])
+ self.assertEquals(mm.getlist('key4'), ['value5', 'value6'])
+ self.assertEquals(mm.getlist('undefined'), [])
+
+ self.assertEquals(sorted(mm.keys()), ['key1', 'key2', 'key4'])
+ self.assertEquals(len(mm.values()), 3)
+
+ self.assertTrue('value1' in mm.values())
+
+ self.assertEquals(sorted(mm.items(), key=lambda k: k[0]),
+ [('key1', 'value1'), ('key2', 'value3'),
+ ('key4', 'value6')])
+
+ self.assertEquals([(k,mm.getlist(k)) for k in sorted(mm)],
+ [('key1', ['value1']),
+ ('key2', ['value2', 'value3']),
+ ('key4', ['value5', 'value6'])])
+
+class MultiValueDictTests(DatastructuresTestCase):
+
+ def test_multivaluedict(self):
+ d = MultiValueDict({'name': ['Adrian', 'Simon'],
+ 'position': ['Developer']})
+
+ self.assertEquals(d['name'], 'Simon')
+ self.assertEquals(d.get('name'), 'Simon')
+ self.assertEquals(d.getlist('name'), ['Adrian', 'Simon'])
+ self.assertEquals(list(d.iteritems()),
+ [('position', 'Developer'), ('name', 'Simon')])
+
+ self.assertEquals(list(d.iterlists()),
+ [('position', ['Developer']),
+ ('name', ['Adrian', 'Simon'])])
+
+ # MultiValueDictKeyError: "Key 'lastname' not found in
+ # <MultiValueDict: {'position': ['Developer'],
+ # 'name': ['Adrian', 'Simon']}>"
+ self.assertRaisesErrorWithMessage(MultiValueDictKeyError,
+ '"Key \'lastname\' not found in <MultiValueDict: {\'position\':'\
+ ' [\'Developer\'], \'name\': [\'Adrian\', \'Simon\']}>"',
+ d.__getitem__, 'lastname')
+
+ self.assertEquals(d.get('lastname'), None)
+ self.assertEquals(d.get('lastname', 'nonexistent'), 'nonexistent')
+ self.assertEquals(d.getlist('lastname'), [])
+
+ d.setlist('lastname', ['Holovaty', 'Willison'])
+ self.assertEquals(d.getlist('lastname'), ['Holovaty', 'Willison'])
+ self.assertEquals(d.values(), ['Developer', 'Simon', 'Willison'])
+ self.assertEquals(list(d.itervalues()),
+ ['Developer', 'Simon', 'Willison'])
+
+
+class DotExpandedDictTests(DatastructuresTestCase):
+
+ def test_dotexpandeddict(self):
+
+ d = DotExpandedDict({'person.1.firstname': ['Simon'],
+ 'person.1.lastname': ['Willison'],
+ 'person.2.firstname': ['Adrian'],
+ 'person.2.lastname': ['Holovaty']})
+
+ self.assertEquals(d['person']['1']['lastname'], ['Willison'])
+ self.assertEquals(d['person']['2']['lastname'], ['Holovaty'])
+ self.assertEquals(d['person']['2']['firstname'], ['Adrian'])
+
+
+class ImmutableListTests(DatastructuresTestCase):
+
+ def test_sort(self):
+ d = ImmutableList(range(10))
+
+ # AttributeError: ImmutableList object is immutable.
+ self.assertRaisesErrorWithMessage(AttributeError,
+ 'ImmutableList object is immutable.', d.sort)
+
+ self.assertEquals(repr(d), '(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)')
+
+ def test_custom_warning(self):
+ d = ImmutableList(range(10), warning="Object is immutable!")
+
+ self.assertEquals(d[1], 1)
+
+ # AttributeError: Object is immutable!
+ self.assertRaisesErrorWithMessage(AttributeError,
+ 'Object is immutable!', d.__setitem__, 1, 'test')
+
+
+class DictWrapperTests(DatastructuresTestCase):
+
+ def test_dictwrapper(self):
+ f = lambda x: "*%s" % x
+ d = DictWrapper({'a': 'a'}, f, 'xx_')
+ self.assertEquals("Normal: %(a)s. Modified: %(xx_a)s" % d,
+ 'Normal: a. Modified: *a')
diff --git a/parts/django/tests/regressiontests/utils/dateformat.py b/parts/django/tests/regressiontests/utils/dateformat.py
new file mode 100644
index 0000000..b312c8d
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/dateformat.py
@@ -0,0 +1,129 @@
+from datetime import datetime, date
+import os
+import time
+import unittest
+
+from django.utils.dateformat import format
+from django.utils import dateformat, translation
+from django.utils.tzinfo import FixedOffset, LocalTimezone
+
+class DateFormatTests(unittest.TestCase):
+ def setUp(self):
+ self.old_TZ = os.environ.get('TZ')
+ os.environ['TZ'] = 'Europe/Copenhagen'
+ translation.activate('en-us')
+
+ try:
+ # Check if a timezone has been set
+ time.tzset()
+ self.tz_tests = True
+ except AttributeError:
+ # No timezone available. Don't run the tests that require a TZ
+ self.tz_tests = False
+
+ def tearDown(self):
+ if self.old_TZ is None:
+ del os.environ['TZ']
+ else:
+ os.environ['TZ'] = self.old_TZ
+
+ # Cleanup - force re-evaluation of TZ environment variable.
+ if self.tz_tests:
+ time.tzset()
+
+ def test_date(self):
+ d = date(2009, 5, 16)
+ self.assertEquals(date.fromtimestamp(int(format(d, 'U'))), d)
+
+ def test_naive_datetime(self):
+ dt = datetime(2009, 5, 16, 5, 30, 30)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt)
+
+ def test_datetime_with_local_tzinfo(self):
+ ltz = LocalTimezone(datetime.now())
+ dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.replace(tzinfo=None))
+
+ def test_datetime_with_tzinfo(self):
+ tz = FixedOffset(-510)
+ ltz = LocalTimezone(datetime.now())
+ dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz), dt)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None))
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple())
+ self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple())
+
+ def test_epoch(self):
+ utc = FixedOffset(0)
+ udt = datetime(1970, 1, 1, tzinfo=utc)
+ self.assertEquals(format(udt, 'U'), u'0')
+
+ def test_empty_format(self):
+ my_birthday = datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, ''), u'')
+
+ def test_am_pm(self):
+ my_birthday = datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, 'a'), u'p.m.')
+
+ def test_date_formats(self):
+ my_birthday = datetime(1979, 7, 8, 22, 00)
+ timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456)
+
+ self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM')
+ self.assertEquals(dateformat.format(timestamp, 'c'), u'2008-05-19T11:45:23.123456')
+ self.assertEquals(dateformat.format(my_birthday, 'd'), u'08')
+ self.assertEquals(dateformat.format(my_birthday, 'j'), u'8')
+ self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday')
+ self.assertEquals(dateformat.format(my_birthday, 'L'), u'False')
+ self.assertEquals(dateformat.format(my_birthday, 'm'), u'07')
+ self.assertEquals(dateformat.format(my_birthday, 'M'), u'Jul')
+ self.assertEquals(dateformat.format(my_birthday, 'b'), u'jul')
+ self.assertEquals(dateformat.format(my_birthday, 'n'), u'7')
+ self.assertEquals(dateformat.format(my_birthday, 'N'), u'July')
+
+ def test_time_formats(self):
+ my_birthday = datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, 'P'), u'10 p.m.')
+ self.assertEquals(dateformat.format(my_birthday, 's'), u'00')
+ self.assertEquals(dateformat.format(my_birthday, 'S'), u'th')
+ self.assertEquals(dateformat.format(my_birthday, 't'), u'31')
+ self.assertEquals(dateformat.format(my_birthday, 'w'), u'0')
+ self.assertEquals(dateformat.format(my_birthday, 'W'), u'27')
+ self.assertEquals(dateformat.format(my_birthday, 'y'), u'79')
+ self.assertEquals(dateformat.format(my_birthday, 'Y'), u'1979')
+ self.assertEquals(dateformat.format(my_birthday, 'z'), u'189')
+
+ def test_dateformat(self):
+ my_birthday = datetime(1979, 7, 8, 22, 00)
+
+ self.assertEquals(dateformat.format(my_birthday, r'Y z \C\E\T'), u'1979 189 CET')
+
+ self.assertEquals(dateformat.format(my_birthday, r'jS o\f F'), u'8th of July')
+
+ def test_futuredates(self):
+ the_future = datetime(2100, 10, 25, 0, 00)
+ self.assertEquals(dateformat.format(the_future, r'Y'), u'2100')
+
+ def test_timezones(self):
+ my_birthday = datetime(1979, 7, 8, 22, 00)
+ summertime = datetime(2005, 10, 30, 1, 00)
+ wintertime = datetime(2005, 10, 30, 4, 00)
+ timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456)
+
+ if self.tz_tests:
+ self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100')
+ self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100')
+ self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET')
+ self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600')
+ self.assertEquals(dateformat.format(timestamp, 'u'), u'123456')
+ self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600')
+ self.assertEquals(dateformat.format(summertime, 'I'), u'1')
+ self.assertEquals(dateformat.format(summertime, 'O'), u'+0200')
+ self.assertEquals(dateformat.format(wintertime, 'I'), u'0')
+ self.assertEquals(dateformat.format(wintertime, 'O'), u'+0100')
diff --git a/parts/django/tests/regressiontests/utils/datetime_safe.py b/parts/django/tests/regressiontests/utils/datetime_safe.py
new file mode 100644
index 0000000..458a6b7
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/datetime_safe.py
@@ -0,0 +1,42 @@
+import unittest
+
+from datetime import date as original_date, datetime as original_datetime
+from django.utils.datetime_safe import date, datetime
+
+class DatetimeTests(unittest.TestCase):
+
+ def setUp(self):
+ self.just_safe = (1900, 1, 1)
+ self.just_unsafe = (1899, 12, 31, 23, 59, 59)
+ self.really_old = (20, 1, 1)
+ self.more_recent = (2006, 1, 1)
+
+ def test_compare_datetimes(self):
+ self.assertEqual(original_datetime(*self.more_recent), datetime(*self.more_recent))
+ self.assertEqual(original_datetime(*self.really_old), datetime(*self.really_old))
+ self.assertEqual(original_date(*self.more_recent), date(*self.more_recent))
+ self.assertEqual(original_date(*self.really_old), date(*self.really_old))
+
+ self.assertEqual(original_date(*self.just_safe).strftime('%Y-%m-%d'), date(*self.just_safe).strftime('%Y-%m-%d'))
+ self.assertEqual(original_datetime(*self.just_safe).strftime('%Y-%m-%d'), datetime(*self.just_safe).strftime('%Y-%m-%d'))
+
+ def test_safe_strftime(self):
+ self.assertEquals(date(*self.just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)'), '1899-12-31 (weekday 0)')
+ self.assertEquals(date(*self.just_safe).strftime('%Y-%m-%d (weekday %w)'), '1900-01-01 (weekday 1)')
+
+ self.assertEquals(datetime(*self.just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1899-12-31 23:59:59 (weekday 0)')
+ self.assertEquals(datetime(*self.just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1900-01-01 00:00:00 (weekday 1)')
+
+ # %y will error before this date
+ self.assertEquals(date(*self.just_safe).strftime('%y'), '00')
+ self.assertEquals(datetime(*self.just_safe).strftime('%y'), '00')
+
+ self.assertEquals(date(1850, 8, 2).strftime("%Y/%m/%d was a %A"), '1850/08/02 was a Friday')
+
+ def test_zero_padding(self):
+ """
+ Regression for #12524
+
+ Check that pre-1000AD dates are padded with zeros if necessary
+ """
+ self.assertEquals(date(1, 1, 1).strftime("%Y/%m/%d was a %A"), '0001/01/01 was a Monday')
diff --git a/parts/django/tests/regressiontests/utils/decorators.py b/parts/django/tests/regressiontests/utils/decorators.py
new file mode 100644
index 0000000..ca9214f
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/decorators.py
@@ -0,0 +1,19 @@
+from django.test import TestCase
+
+class DecoratorFromMiddlewareTests(TestCase):
+ """
+ Tests for view decorators created using
+ ``django.utils.decorators.decorator_from_middleware``.
+ """
+
+ def test_process_view_middleware(self):
+ """
+ Test a middleware that implements process_view.
+ """
+ self.client.get('/utils/xview/')
+
+ def test_callable_process_view_middleware(self):
+ """
+ Test a middleware that implements process_view, operating on a callable class.
+ """
+ self.client.get('/utils/class_xview/')
diff --git a/parts/django/tests/regressiontests/utils/eggs/test_egg.egg b/parts/django/tests/regressiontests/utils/eggs/test_egg.egg
new file mode 100644
index 0000000..9b08cc1
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/eggs/test_egg.egg
Binary files differ
diff --git a/parts/django/tests/regressiontests/utils/feedgenerator.py b/parts/django/tests/regressiontests/utils/feedgenerator.py
new file mode 100644
index 0000000..9085d41
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/feedgenerator.py
@@ -0,0 +1,63 @@
+import datetime
+import unittest
+
+from django.utils import feedgenerator, tzinfo
+
+class FeedgeneratorTest(unittest.TestCase):
+ """
+ Tests for the low-level syndication feed framework.
+ """
+
+ def test_get_tag_uri(self):
+ """
+ Test get_tag_uri() correctly generates TagURIs.
+ """
+ self.assertEqual(
+ feedgenerator.get_tag_uri('http://example.org/foo/bar#headline', datetime.date(2004, 10, 25)),
+ u'tag:example.org,2004-10-25:/foo/bar/headline')
+
+ def test_get_tag_uri_with_port(self):
+ """
+ Test that get_tag_uri() correctly generates TagURIs from URLs with port
+ numbers.
+ """
+ self.assertEqual(
+ feedgenerator.get_tag_uri('http://www.example.org:8000/2008/11/14/django#headline', datetime.datetime(2008, 11, 14, 13, 37, 0)),
+ u'tag:www.example.org,2008-11-14:/2008/11/14/django/headline')
+
+ def test_rfc2822_date(self):
+ """
+ Test rfc2822_date() correctly formats datetime objects.
+ """
+ self.assertEqual(
+ feedgenerator.rfc2822_date(datetime.datetime(2008, 11, 14, 13, 37, 0)),
+ "Fri, 14 Nov 2008 13:37:00 -0000"
+ )
+
+ def test_rfc2822_date_with_timezone(self):
+ """
+ Test rfc2822_date() correctly formats datetime objects with tzinfo.
+ """
+ self.assertEqual(
+ feedgenerator.rfc2822_date(datetime.datetime(2008, 11, 14, 13, 37, 0, tzinfo=tzinfo.FixedOffset(datetime.timedelta(minutes=60)))),
+ "Fri, 14 Nov 2008 13:37:00 +0100"
+ )
+
+ def test_rfc3339_date(self):
+ """
+ Test rfc3339_date() correctly formats datetime objects.
+ """
+ self.assertEqual(
+ feedgenerator.rfc3339_date(datetime.datetime(2008, 11, 14, 13, 37, 0)),
+ "2008-11-14T13:37:00Z"
+ )
+
+ def test_rfc3339_date_with_timezone(self):
+ """
+ Test rfc3339_date() correctly formats datetime objects with tzinfo.
+ """
+ self.assertEqual(
+ feedgenerator.rfc3339_date(datetime.datetime(2008, 11, 14, 13, 37, 0, tzinfo=tzinfo.FixedOffset(datetime.timedelta(minutes=120)))),
+ "2008-11-14T13:37:00+02:00"
+ )
+
diff --git a/parts/django/tests/regressiontests/utils/functional.py b/parts/django/tests/regressiontests/utils/functional.py
new file mode 100644
index 0000000..206a583
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/functional.py
@@ -0,0 +1,10 @@
+import unittest
+
+from django.utils.functional import lazy
+
+
+class FunctionalTestCase(unittest.TestCase):
+ def test_lazy(self):
+ t = lazy(lambda: tuple(range(3)), list, tuple)
+ for a, b in zip(t(), range(3)):
+ self.assertEqual(a, b)
diff --git a/parts/django/tests/regressiontests/utils/html.py b/parts/django/tests/regressiontests/utils/html.py
new file mode 100644
index 0000000..a9b0d33
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/html.py
@@ -0,0 +1,111 @@
+import unittest
+
+from django.utils import html
+
+class TestUtilsHtml(unittest.TestCase):
+
+ def check_output(self, function, value, output=None):
+ """
+ Check that function(value) equals output. If output is None,
+ check that function(value) equals value.
+ """
+ if output is None:
+ output = value
+ self.assertEqual(function(value), output)
+
+ def test_escape(self):
+ f = html.escape
+ items = (
+ ('&','&amp;'),
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ ('"', '&quot;'),
+ ("'", '&#39;'),
+ )
+ # Substitution patterns for testing the above items.
+ patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb")
+ for value, output in items:
+ for pattern in patterns:
+ self.check_output(f, pattern % value, pattern % output)
+ # Check repeated values.
+ self.check_output(f, value * 2, output * 2)
+ # Verify it doesn't double replace &.
+ self.check_output(f, '<&', '&lt;&amp;')
+
+ def test_linebreaks(self):
+ f = html.linebreaks
+ items = (
+ ("para1\n\npara2\r\rpara3", "<p>para1</p>\n\n<p>para2</p>\n\n<p>para3</p>"),
+ ("para1\nsub1\rsub2\n\npara2", "<p>para1<br />sub1<br />sub2</p>\n\n<p>para2</p>"),
+ ("para1\r\n\r\npara2\rsub1\r\rpara4", "<p>para1</p>\n\n<p>para2<br />sub1</p>\n\n<p>para4</p>"),
+ ("para1\tmore\n\npara2", "<p>para1\tmore</p>\n\n<p>para2</p>"),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
+
+ def test_strip_tags(self):
+ f = html.strip_tags
+ items = (
+ ('<adf>a', 'a'),
+ ('</adf>a', 'a'),
+ ('<asdf><asdf>e', 'e'),
+ ('<f', '<f'),
+ ('</fe', '</fe'),
+ ('<x>b<y>', 'b'),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
+
+ def test_strip_spaces_between_tags(self):
+ f = html.strip_spaces_between_tags
+ # Strings that should come out untouched.
+ items = (' <adf>', '<adf> ', ' </adf> ', ' <f> x</f>')
+ for value in items:
+ self.check_output(f, value)
+ # Strings that have spaces to strip.
+ items = (
+ ('<d> </d>', '<d></d>'),
+ ('<p>hello </p>\n<p> world</p>', '<p>hello </p><p> world</p>'),
+ ('\n<p>\t</p>\n<p> </p>\n', '\n<p></p><p></p>\n'),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
+
+ def test_strip_entities(self):
+ f = html.strip_entities
+ # Strings that should come out untouched.
+ values = ("&", "&a", "&a", "a&#a")
+ for value in values:
+ self.check_output(f, value)
+ # Valid entities that should be stripped from the patterns.
+ entities = ("&#1;", "&#12;", "&a;", "&fdasdfasdfasdf;")
+ patterns = (
+ ("asdf %(entity)s ", "asdf "),
+ ("%(entity)s%(entity)s", ""),
+ ("&%(entity)s%(entity)s", "&"),
+ ("%(entity)s3", "3"),
+ )
+ for entity in entities:
+ for in_pattern, output in patterns:
+ self.check_output(f, in_pattern % {'entity': entity}, output)
+
+ def test_fix_ampersands(self):
+ f = html.fix_ampersands
+ # Strings without ampersands or with ampersands already encoded.
+ values = ("a&#1;", "b", "&a;", "&amp; &x; ", "asdf")
+ patterns = (
+ ("%s", "%s"),
+ ("&%s", "&amp;%s"),
+ ("&%s&", "&amp;%s&amp;"),
+ )
+ for value in values:
+ for in_pattern, out_pattern in patterns:
+ self.check_output(f, in_pattern % value, out_pattern % value)
+ # Strings with ampersands that need encoding.
+ items = (
+ ("&#;", "&amp;#;"),
+ ("&#875 ;", "&amp;#875 ;"),
+ ("&#4abc;", "&amp;#4abc;"),
+ )
+ for value, output in items:
+ self.check_output(f, value, output)
diff --git a/parts/django/tests/regressiontests/utils/models.py b/parts/django/tests/regressiontests/utils/models.py
new file mode 100644
index 0000000..97a72ba
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/models.py
@@ -0,0 +1 @@
+# Test runner needs a models.py file.
diff --git a/parts/django/tests/regressiontests/utils/module_loading.py b/parts/django/tests/regressiontests/utils/module_loading.py
new file mode 100644
index 0000000..8cbefbb
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/module_loading.py
@@ -0,0 +1,114 @@
+import os
+import sys
+import unittest
+from zipimport import zipimporter
+
+from django.utils.importlib import import_module
+from django.utils.module_loading import module_has_submodule
+
+class DefaultLoader(unittest.TestCase):
+ def test_loader(self):
+ "Normal module existence can be tested"
+ test_module = import_module('regressiontests.utils.test_module')
+
+ # An importable child
+ self.assertTrue(module_has_submodule(test_module, 'good_module'))
+ mod = import_module('regressiontests.utils.test_module.good_module')
+ self.assertEqual(mod.content, 'Good Module')
+
+ # A child that exists, but will generate an import error if loaded
+ self.assertTrue(module_has_submodule(test_module, 'bad_module'))
+ self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.bad_module')
+
+ # A child that doesn't exist
+ self.assertFalse(module_has_submodule(test_module, 'no_such_module'))
+ self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.no_such_module')
+
+class EggLoader(unittest.TestCase):
+ def setUp(self):
+ self.old_path = sys.path[:]
+ self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
+
+ def tearDown(self):
+ sys.path = self.old_path
+ sys.path_importer_cache.clear()
+
+ sys.modules.pop('egg_module.sub1.sub2.bad_module', None)
+ sys.modules.pop('egg_module.sub1.sub2.good_module', None)
+ sys.modules.pop('egg_module.sub1.sub2', None)
+ sys.modules.pop('egg_module.sub1', None)
+ sys.modules.pop('egg_module.bad_module', None)
+ sys.modules.pop('egg_module.good_module', None)
+ sys.modules.pop('egg_module', None)
+
+ def test_shallow_loader(self):
+ "Module existence can be tested inside eggs"
+ egg_name = '%s/test_egg.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ egg_module = import_module('egg_module')
+
+ # An importable child
+ self.assertTrue(module_has_submodule(egg_module, 'good_module'))
+ mod = import_module('egg_module.good_module')
+ self.assertEqual(mod.content, 'Good Module')
+
+ # A child that exists, but will generate an import error if loaded
+ self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
+ self.assertRaises(ImportError, import_module, 'egg_module.bad_module')
+
+ # A child that doesn't exist
+ self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
+ self.assertRaises(ImportError, import_module, 'egg_module.no_such_module')
+
+ def test_deep_loader(self):
+ "Modules deep inside an egg can still be tested for existence"
+ egg_name = '%s/test_egg.egg' % self.egg_dir
+ sys.path.append(egg_name)
+ egg_module = import_module('egg_module.sub1.sub2')
+
+ # An importable child
+ self.assertTrue(module_has_submodule(egg_module, 'good_module'))
+ mod = import_module('egg_module.sub1.sub2.good_module')
+ self.assertEqual(mod.content, 'Deep Good Module')
+
+ # A child that exists, but will generate an import error if loaded
+ self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
+ self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module')
+
+ # A child that doesn't exist
+ self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
+ self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
+
+class TestFinder(object):
+ def __init__(self, *args, **kwargs):
+ self.importer = zipimporter(*args, **kwargs)
+
+ def find_module(self, path):
+ importer = self.importer.find_module(path)
+ if importer is None:
+ return
+ return TestLoader(importer)
+
+class TestLoader(object):
+ def __init__(self, importer):
+ self.importer = importer
+
+ def load_module(self, name):
+ mod = self.importer.load_module(name)
+ mod.__loader__ = self
+ return mod
+
+class CustomLoader(EggLoader):
+ """The Custom Loader test is exactly the same as the EggLoader, but
+ it uses a custom defined Loader and Finder that is intentionally
+ split into two classes. Although the EggLoader combines both functions
+ into one class, this isn't required.
+ """
+ def setUp(self):
+ super(CustomLoader, self).setUp()
+ sys.path_hooks.insert(0, TestFinder)
+ sys.path_importer_cache.clear()
+
+ def tearDown(self):
+ super(CustomLoader, self).tearDown()
+ sys.path_hooks.pop(0)
diff --git a/parts/django/tests/regressiontests/utils/simplelazyobject.py b/parts/django/tests/regressiontests/utils/simplelazyobject.py
new file mode 100644
index 0000000..4a930dd
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/simplelazyobject.py
@@ -0,0 +1,77 @@
+import unittest
+
+import django.utils.copycompat as copy
+from django.utils.functional import SimpleLazyObject
+
+class _ComplexObject(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __eq__(self, other):
+ return self.name == other.name
+
+ def __hash__(self):
+ return hash(self.name)
+
+ def __str__(self):
+ return "I am _ComplexObject(%r)" % self.name
+
+ def __unicode__(self):
+ return unicode(self.name)
+
+ def __repr__(self):
+ return "_ComplexObject(%r)" % self.name
+
+complex_object = lambda: _ComplexObject("joe")
+
+class TestUtilsSimpleLazyObject(unittest.TestCase):
+ """
+ Tests for SimpleLazyObject
+ """
+ # Note that concrete use cases for SimpleLazyObject are also found in the
+ # auth context processor tests (unless the implementation of that function
+ # is changed).
+
+ def test_equality(self):
+ self.assertEqual(complex_object(), SimpleLazyObject(complex_object))
+ self.assertEqual(SimpleLazyObject(complex_object), complex_object())
+
+ def test_hash(self):
+ # hash() equality would not be true for many objects, but it should be
+ # for _ComplexObject
+ self.assertEqual(hash(complex_object()),
+ hash(SimpleLazyObject(complex_object)))
+
+ def test_repr(self):
+ # For debugging, it will really confuse things if there is no clue that
+ # SimpleLazyObject is actually a proxy object. So we don't
+ # proxy __repr__
+ self.assert_("SimpleLazyObject" in repr(SimpleLazyObject(complex_object)))
+
+ def test_str(self):
+ self.assertEqual("I am _ComplexObject('joe')", str(SimpleLazyObject(complex_object)))
+
+ def test_unicode(self):
+ self.assertEqual(u"joe", unicode(SimpleLazyObject(complex_object)))
+
+ def test_class(self):
+ # This is important for classes that use __class__ in things like
+ # equality tests.
+ self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__)
+
+ def test_deepcopy(self):
+ # Check that we *can* do deep copy, and that it returns the right
+ # objects.
+
+ # First, for an unevaluated SimpleLazyObject
+ s = SimpleLazyObject(complex_object)
+ assert s._wrapped is None
+ s2 = copy.deepcopy(s)
+ assert s._wrapped is None # something has gone wrong is s is evaluated
+ self.assertEqual(s2, complex_object())
+
+ # Second, for an evaluated SimpleLazyObject
+ name = s.name # evaluate
+ assert s._wrapped is not None
+ s3 = copy.deepcopy(s)
+ self.assertEqual(s3, complex_object())
diff --git a/parts/django/tests/regressiontests/utils/termcolors.py b/parts/django/tests/regressiontests/utils/termcolors.py
new file mode 100644
index 0000000..ccae32c
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/termcolors.py
@@ -0,0 +1,149 @@
+import unittest
+
+from django.utils.termcolors import parse_color_setting, PALETTES, DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE
+
+class TermColorTests(unittest.TestCase):
+
+ def test_empty_string(self):
+ self.assertEquals(parse_color_setting(''), PALETTES[DEFAULT_PALETTE])
+
+ def test_simple_palette(self):
+ self.assertEquals(parse_color_setting('light'), PALETTES[LIGHT_PALETTE])
+ self.assertEquals(parse_color_setting('dark'), PALETTES[DARK_PALETTE])
+ self.assertEquals(parse_color_setting('nocolor'), None)
+
+ def test_fg(self):
+ self.assertEquals(parse_color_setting('error=green'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+
+ def test_fg_bg(self):
+ self.assertEquals(parse_color_setting('error=green/blue'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg':'blue'}))
+
+ def test_fg_opts(self):
+ self.assertEquals(parse_color_setting('error=green,blink'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'opts': ('blink',)}))
+ self.assertEquals(parse_color_setting('error=green,bold,blink'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'opts': ('blink','bold')}))
+
+ def test_fg_bg_opts(self):
+ self.assertEquals(parse_color_setting('error=green/blue,blink'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink',)}))
+ self.assertEquals(parse_color_setting('error=green/blue,bold,blink'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink','bold')}))
+
+ def test_override_palette(self):
+ self.assertEquals(parse_color_setting('light;error=green'),
+ dict(PALETTES[LIGHT_PALETTE],
+ ERROR={'fg':'green'}))
+
+ def test_override_nocolor(self):
+ self.assertEquals(parse_color_setting('nocolor;error=green'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg': 'green'}))
+
+ def test_reverse_override(self):
+ self.assertEquals(parse_color_setting('error=green;light'), PALETTES[LIGHT_PALETTE])
+
+ def test_multiple_roles(self):
+ self.assertEquals(parse_color_setting('error=green;sql_field=blue'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'},
+ SQL_FIELD={'fg':'blue'}))
+
+ def test_override_with_multiple_roles(self):
+ self.assertEquals(parse_color_setting('light;error=green;sql_field=blue'),
+ dict(PALETTES[LIGHT_PALETTE],
+ ERROR={'fg':'green'},
+ SQL_FIELD={'fg':'blue'}))
+
+ def test_empty_definition(self):
+ self.assertEquals(parse_color_setting(';'), None)
+ self.assertEquals(parse_color_setting('light;'), PALETTES[LIGHT_PALETTE])
+ self.assertEquals(parse_color_setting(';;;'), None)
+
+ def test_empty_options(self):
+ self.assertEquals(parse_color_setting('error=green,'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('error=green,,,'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('error=green,,blink,,'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'opts': ('blink',)}))
+
+ def test_bad_palette(self):
+ self.assertEquals(parse_color_setting('unknown'), None)
+
+ def test_bad_role(self):
+ self.assertEquals(parse_color_setting('unknown='), None)
+ self.assertEquals(parse_color_setting('unknown=green'), None)
+ self.assertEquals(parse_color_setting('unknown=green;sql_field=blue'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ SQL_FIELD={'fg':'blue'}))
+
+ def test_bad_color(self):
+ self.assertEquals(parse_color_setting('error='), None)
+ self.assertEquals(parse_color_setting('error=;sql_field=blue'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ SQL_FIELD={'fg':'blue'}))
+ self.assertEquals(parse_color_setting('error=unknown'), None)
+ self.assertEquals(parse_color_setting('error=unknown;sql_field=blue'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ SQL_FIELD={'fg':'blue'}))
+ self.assertEquals(parse_color_setting('error=green/unknown'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('error=green/blue/something'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg': 'blue'}))
+ self.assertEquals(parse_color_setting('error=green/blue/something,blink'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg': 'blue', 'opts': ('blink',)}))
+
+ def test_bad_option(self):
+ self.assertEquals(parse_color_setting('error=green,unknown'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('error=green,unknown,blink'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'opts': ('blink',)}))
+
+ def test_role_case(self):
+ self.assertEquals(parse_color_setting('ERROR=green'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('eRrOr=green'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+
+ def test_color_case(self):
+ self.assertEquals(parse_color_setting('error=GREEN'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('error=GREEN/BLUE'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg':'blue'}))
+
+ self.assertEquals(parse_color_setting('error=gReEn'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green'}))
+ self.assertEquals(parse_color_setting('error=gReEn/bLuE'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'bg':'blue'}))
+
+ def test_opts_case(self):
+ self.assertEquals(parse_color_setting('error=green,BLINK'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'opts': ('blink',)}))
+
+ self.assertEquals(parse_color_setting('error=green,bLiNk'),
+ dict(PALETTES[NOCOLOR_PALETTE],
+ ERROR={'fg':'green', 'opts': ('blink',)}))
diff --git a/parts/django/tests/regressiontests/utils/test_module/__init__.py b/parts/django/tests/regressiontests/utils/test_module/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/test_module/__init__.py
diff --git a/parts/django/tests/regressiontests/utils/test_module/bad_module.py b/parts/django/tests/regressiontests/utils/test_module/bad_module.py
new file mode 100644
index 0000000..cc0cd16
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/test_module/bad_module.py
@@ -0,0 +1,3 @@
+import a_package_name_that_does_not_exist
+
+content = 'Bad Module' \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/utils/test_module/good_module.py b/parts/django/tests/regressiontests/utils/test_module/good_module.py
new file mode 100644
index 0000000..0ca6898
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/test_module/good_module.py
@@ -0,0 +1 @@
+content = 'Good Module' \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/utils/tests.py b/parts/django/tests/regressiontests/utils/tests.py
new file mode 100644
index 0000000..6d3bbfa
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/tests.py
@@ -0,0 +1,18 @@
+"""
+Tests for django.utils.
+"""
+
+from dateformat import *
+from feedgenerator import *
+from module_loading import *
+from termcolors import *
+from html import *
+from checksums import *
+from text import *
+from simplelazyobject import *
+from decorators import *
+from functional import *
+from timesince import *
+from datastructures import *
+from tzinfo import *
+from datetime_safe import *
diff --git a/parts/django/tests/regressiontests/utils/text.py b/parts/django/tests/regressiontests/utils/text.py
new file mode 100644
index 0000000..e7d2d38
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/text.py
@@ -0,0 +1,20 @@
+import unittest
+
+from django.utils import text
+
+class TestUtilsText(unittest.TestCase):
+ def test_truncate_words(self):
+ self.assertEqual(u'The quick brown fox jumped over the lazy dog.',
+ text.truncate_words(u'The quick brown fox jumped over the lazy dog.', 10))
+ self.assertEqual(u'The quick brown fox ...',
+ text.truncate_words('The quick brown fox jumped over the lazy dog.', 4))
+ self.assertEqual(u'The quick brown fox ....',
+ text.truncate_words('The quick brown fox jumped over the lazy dog.', 4, '....'))
+ self.assertEqual(u'<p><strong><em>The quick brown fox jumped over the lazy dog.</em></strong></p>',
+ text.truncate_html_words('<p><strong><em>The quick brown fox jumped over the lazy dog.</em></strong></p>', 10))
+ self.assertEqual(u'<p><strong><em>The quick brown fox ...</em></strong></p>',
+ text.truncate_html_words('<p><strong><em>The quick brown fox jumped over the lazy dog.</em></strong></p>', 4))
+ self.assertEqual(u'<p><strong><em>The quick brown fox ....</em></strong></p>',
+ text.truncate_html_words('<p><strong><em>The quick brown fox jumped over the lazy dog.</em></strong></p>', 4, '....'))
+ self.assertEqual(u'<p><strong><em>The quick brown fox</em></strong></p>',
+ text.truncate_html_words('<p><strong><em>The quick brown fox jumped over the lazy dog.</em></strong></p>', 4, None))
diff --git a/parts/django/tests/regressiontests/utils/timesince.py b/parts/django/tests/regressiontests/utils/timesince.py
new file mode 100644
index 0000000..774aa3f
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/timesince.py
@@ -0,0 +1,107 @@
+import datetime
+import unittest
+
+from django.utils.timesince import timesince, timeuntil
+from django.utils.tzinfo import LocalTimezone, FixedOffset
+
+class TimesinceTests(unittest.TestCase):
+
+ def setUp(self):
+ self.t = datetime.datetime(2007, 8, 14, 13, 46, 0)
+ self.onemicrosecond = datetime.timedelta(microseconds=1)
+ self.onesecond = datetime.timedelta(seconds=1)
+ self.oneminute = datetime.timedelta(minutes=1)
+ self.onehour = datetime.timedelta(hours=1)
+ self.oneday = datetime.timedelta(days=1)
+ self.oneweek = datetime.timedelta(days=7)
+ self.onemonth = datetime.timedelta(days=30)
+ self.oneyear = datetime.timedelta(days=365)
+
+ def test_equal_datetimes(self):
+ """ equal datetimes. """
+ self.assertEquals(timesince(self.t, self.t), u'0 minutes')
+
+ def test_ignore_microseconds_and_seconds(self):
+ """ Microseconds and seconds are ignored. """
+ self.assertEquals(timesince(self.t, self.t+self.onemicrosecond),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t+self.onesecond),
+ u'0 minutes')
+
+ def test_other_units(self):
+ """ Test other units. """
+ self.assertEquals(timesince(self.t, self.t+self.oneminute),
+ u'1 minute')
+ self.assertEquals(timesince(self.t, self.t+self.onehour), u'1 hour')
+ self.assertEquals(timesince(self.t, self.t+self.oneday), u'1 day')
+ self.assertEquals(timesince(self.t, self.t+self.oneweek), u'1 week')
+ self.assertEquals(timesince(self.t, self.t+self.onemonth),
+ u'1 month')
+ self.assertEquals(timesince(self.t, self.t+self.oneyear), u'1 year')
+
+ def test_multiple_units(self):
+ """ Test multiple units. """
+ self.assertEquals(timesince(self.t,
+ self.t+2*self.oneday+6*self.onehour), u'2 days, 6 hours')
+ self.assertEquals(timesince(self.t,
+ self.t+2*self.oneweek+2*self.oneday), u'2 weeks, 2 days')
+
+ def test_display_first_unit(self):
+ """
+ If the two differing units aren't adjacent, only the first unit is
+ displayed.
+ """
+ self.assertEquals(timesince(self.t,
+ self.t+2*self.oneweek+3*self.onehour+4*self.oneminute),
+ u'2 weeks')
+
+ self.assertEquals(timesince(self.t,
+ self.t+4*self.oneday+5*self.oneminute), u'4 days')
+
+ def test_display_second_before_first(self):
+ """
+ When the second date occurs before the first, we should always
+ get 0 minutes.
+ """
+ self.assertEquals(timesince(self.t, self.t-self.onemicrosecond),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.onesecond),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.oneminute),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.onehour),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.oneday),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.oneweek),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.onemonth),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t, self.t-self.oneyear),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t,
+ self.t-2*self.oneday-6*self.onehour), u'0 minutes')
+ self.assertEquals(timesince(self.t,
+ self.t-2*self.oneweek-2*self.oneday), u'0 minutes')
+ self.assertEquals(timesince(self.t,
+ self.t-2*self.oneweek-3*self.onehour-4*self.oneminute),
+ u'0 minutes')
+ self.assertEquals(timesince(self.t,
+ self.t-4*self.oneday-5*self.oneminute), u'0 minutes')
+
+ def test_different_timezones(self):
+ """ When using two different timezones. """
+ now = datetime.datetime.now()
+ now_tz = datetime.datetime.now(LocalTimezone(now))
+ now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15))
+
+ self.assertEquals(timesince(now), u'0 minutes')
+ self.assertEquals(timesince(now_tz), u'0 minutes')
+ self.assertEquals(timeuntil(now_tz, now_tz_i), u'0 minutes')
+
+ def test_both_date_objects(self):
+ """ Timesince should work with both date objects (#9672) """
+ today = datetime.date.today()
+ self.assertEquals(timeuntil(today+self.oneday, today), u'1 day')
+ self.assertEquals(timeuntil(today-self.oneday, today), u'0 minutes')
+ self.assertEquals(timeuntil(today+self.oneweek, today), u'1 week')
diff --git a/parts/django/tests/regressiontests/utils/tzinfo.py b/parts/django/tests/regressiontests/utils/tzinfo.py
new file mode 100644
index 0000000..edbb9a7
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/tzinfo.py
@@ -0,0 +1,18 @@
+import unittest
+
+from django.utils.tzinfo import FixedOffset
+
+class TzinfoTests(unittest.TestCase):
+
+ def test_fixedoffset(self):
+ self.assertEquals(repr(FixedOffset(0)), '+0000')
+ self.assertEquals(repr(FixedOffset(60)), '+0100')
+ self.assertEquals(repr(FixedOffset(-60)), '-0100')
+ self.assertEquals(repr(FixedOffset(280)), '+0440')
+ self.assertEquals(repr(FixedOffset(-280)), '-0440')
+ self.assertEquals(repr(FixedOffset(-78.4)), '-0118')
+ self.assertEquals(repr(FixedOffset(78.4)), '+0118')
+ self.assertEquals(repr(FixedOffset(-5.5*60)), '-0530')
+ self.assertEquals(repr(FixedOffset(5.5*60)), '+0530')
+ self.assertEquals(repr(FixedOffset(-.5*60)), '-0030')
+ self.assertEquals(repr(FixedOffset(.5*60)), '+0030')
diff --git a/parts/django/tests/regressiontests/utils/urls.py b/parts/django/tests/regressiontests/utils/urls.py
new file mode 100644
index 0000000..ba09d14
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+
+import views
+
+urlpatterns = patterns('',
+ (r'^xview/$', views.xview),
+ (r'^class_xview/$', views.class_xview),
+)
diff --git a/parts/django/tests/regressiontests/utils/views.py b/parts/django/tests/regressiontests/utils/views.py
new file mode 100644
index 0000000..ef97c65
--- /dev/null
+++ b/parts/django/tests/regressiontests/utils/views.py
@@ -0,0 +1,17 @@
+from django.http import HttpResponse
+from django.utils.decorators import decorator_from_middleware
+from django.middleware.doc import XViewMiddleware
+
+
+xview_dec = decorator_from_middleware(XViewMiddleware)
+
+def xview(request):
+ return HttpResponse()
+xview = xview_dec(xview)
+
+
+class ClassXView(object):
+ def __call__(self, request):
+ return HttpResponse()
+
+class_xview = xview_dec(ClassXView())
diff --git a/parts/django/tests/regressiontests/views/__init__.py b/parts/django/tests/regressiontests/views/__init__.py
new file mode 100644
index 0000000..d1c6e08
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf8 -*-
+
+class BrokenException(Exception):
+ pass
+
+except_args = ('Broken!', # plain exception with ASCII text
+ u'¡Broken!', # non-ASCII unicode data
+ '¡Broken!', # non-ASCII, utf-8 encoded bytestring
+ '\xa1Broken!', ) # non-ASCII, latin1 bytestring
+
diff --git a/parts/django/tests/regressiontests/views/app0/__init__.py b/parts/django/tests/regressiontests/views/app0/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app0/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..662204a
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..a458935
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po
@@ -0,0 +1,20 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/app1/__init__.py b/parts/django/tests/regressiontests/views/app1/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app1/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..5d6aecb
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..a4627db
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,20 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/app2/__init__.py b/parts/django/tests/regressiontests/views/app2/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app2/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..17e1863
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..637b9e6
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,20 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/app3/__init__.py b/parts/django/tests/regressiontests/views/app3/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app3/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..0c485a9
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..1e3be0b
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po
@@ -0,0 +1,20 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/app4/__init__.py b/parts/django/tests/regressiontests/views/app4/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app4/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..581fbb0
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..27403c0
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po
@@ -0,0 +1,20 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/fixtures/testdata.json b/parts/django/tests/regressiontests/views/fixtures/testdata.json
new file mode 100644
index 0000000..ab68407
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/fixtures/testdata.json
@@ -0,0 +1,75 @@
+[
+ {
+ "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/parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..b6b0887
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..669af4b
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po
@@ -0,0 +1,25 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..356147c
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..0d03f95
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po
@@ -0,0 +1,24 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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"
diff --git a/parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo b/parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000..21659a9
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po b/parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000..4ea193a
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po
@@ -0,0 +1,24 @@
+# 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\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/parts/django/tests/regressiontests/views/media/file.txt b/parts/django/tests/regressiontests/views/media/file.txt
new file mode 100644
index 0000000..f1fc82c
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/media/file.txt
@@ -0,0 +1 @@
+An example media file. \ No newline at end of file
diff --git a/parts/django/tests/regressiontests/views/media/file.txt.gz b/parts/django/tests/regressiontests/views/media/file.txt.gz
new file mode 100644
index 0000000..0ee7d18
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/media/file.txt.gz
Binary files differ
diff --git a/parts/django/tests/regressiontests/views/media/file.unknown b/parts/django/tests/regressiontests/views/media/file.unknown
new file mode 100644
index 0000000..77dcda8
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/media/file.unknown
@@ -0,0 +1 @@
+An unknown file extension.
diff --git a/parts/django/tests/regressiontests/views/models.py b/parts/django/tests/regressiontests/views/models.py
new file mode 100644
index 0000000..54f5c1c
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/models.py
@@ -0,0 +1,49 @@
+"""
+Regression tests for Django built-in views.
+"""
+
+from django.db import models
+
+class Author(models.Model):
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return '/views/authors/%s/' % self.id
+
+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 __unicode__(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/parts/django/tests/regressiontests/views/templates/debug/template_exception.html b/parts/django/tests/regressiontests/views/templates/debug/template_exception.html
new file mode 100644
index 0000000..c6b34a8
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/templates/debug/template_exception.html
@@ -0,0 +1,2 @@
+{% load debugtags %}
+{% go_boom arg %}
diff --git a/parts/django/tests/regressiontests/views/templatetags/__init__.py b/parts/django/tests/regressiontests/views/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/templatetags/__init__.py
diff --git a/parts/django/tests/regressiontests/views/templatetags/debugtags.py b/parts/django/tests/regressiontests/views/templatetags/debugtags.py
new file mode 100644
index 0000000..9b2c661
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/templatetags/debugtags.py
@@ -0,0 +1,10 @@
+from django import template
+
+from regressiontests.views import BrokenException
+
+register = template.Library()
+
+@register.simple_tag
+def go_boom(arg):
+ raise BrokenException(arg)
+
diff --git a/parts/django/tests/regressiontests/views/tests/__init__.py b/parts/django/tests/regressiontests/views/tests/__init__.py
new file mode 100644
index 0000000..697968e
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/__init__.py
@@ -0,0 +1,7 @@
+from debug import *
+from defaults import *
+from generic.create_update import *
+from generic.date_based import *
+from i18n import *
+from specials import *
+from static import *
diff --git a/parts/django/tests/regressiontests/views/tests/debug.py b/parts/django/tests/regressiontests/views/tests/debug.py
new file mode 100644
index 0000000..4ebe37f
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/debug.py
@@ -0,0 +1,50 @@
+import inspect
+
+from django.conf import settings
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from django.template import TemplateSyntaxError
+
+from regressiontests.views import BrokenException, except_args
+
+class DebugViewTests(TestCase):
+ def setUp(self):
+ self.old_debug = settings.DEBUG
+ settings.DEBUG = True
+ self.old_template_debug = settings.TEMPLATE_DEBUG
+ settings.TEMPLATE_DEBUG = True
+
+ def tearDown(self):
+ settings.DEBUG = self.old_debug
+ settings.TEMPLATE_DEBUG = self.old_template_debug
+
+ def test_files(self):
+ response = self.client.get('/views/raises/')
+ self.assertEquals(response.status_code, 500)
+
+ data = {
+ 'file_data.txt': SimpleUploadedFile('file_data.txt', 'haha'),
+ }
+ response = self.client.post('/views/raises/', data)
+ self.assertTrue('file_data.txt' in response.content)
+ self.assertFalse('haha' in response.content)
+
+ def test_404(self):
+ response = self.client.get('/views/raises404/')
+ self.assertEquals(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 TemplateSyntaxError, e:
+ 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)
diff --git a/parts/django/tests/regressiontests/views/tests/defaults.py b/parts/django/tests/regressiontests/views/tests/defaults.py
new file mode 100644
index 0000000..ffc3471
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/defaults.py
@@ -0,0 +1,85 @@
+from os import path
+
+from django.conf import settings
+from django.test import TestCase
+from django.contrib.contenttypes.models import ContentType
+
+from regressiontests.views.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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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
+ old_DEBUG = settings.DEBUG
+ try:
+ settings.DEBUG = False # so we get real 404, not technical
+ 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), '')
+ finally:
+ settings.DEBUG = old_DEBUG
+
+ def test_server_error(self):
+ "The server_error view raises a 500 status"
+ response = self.client.get('/views/server_error/')
+ self.assertEquals(response.status_code, 500)
+
+ 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/parts/django/tests/regressiontests/views/tests/generic/__init__.py b/parts/django/tests/regressiontests/views/tests/generic/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/generic/__init__.py
diff --git a/parts/django/tests/regressiontests/views/tests/generic/create_update.py b/parts/django/tests/regressiontests/views/tests/generic/create_update.py
new file mode 100644
index 0000000..4ba1c35
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/generic/create_update.py
@@ -0,0 +1,211 @@
+import datetime
+
+from django.test import TestCase
+from django.core.exceptions import ImproperlyConfigured
+from regressiontests.views.models import Article, UrlArticle
+
+class CreateObjectTest(TestCase):
+
+ fixtures = ['testdata.json']
+
+ def test_login_required_view(self):
+ """
+ Verifies that an unauthenticated user attempting to access a
+ login_required view gets redirected to the login page and that
+ an authenticated user is let through.
+ """
+ view_url = '/views/create_update/member/create/article/'
+ response = self.client.get(view_url)
+ self.assertRedirects(response, '/accounts/login/?next=%s' % view_url)
+ # Now login and try again.
+ login = self.client.login(username='testclient', password='password')
+ self.assertTrue(login, 'Could not log in')
+ response = self.client.get(view_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'views/article_form.html')
+
+ def test_create_article_display_page(self):
+ """
+ Ensures the generic view returned the page and contains a form.
+ """
+ view_url = '/views/create_update/create/article/'
+ response = self.client.get(view_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'views/article_form.html')
+ if not response.context.get('form'):
+ self.fail('No form found in the response.')
+
+ def test_create_article_with_errors(self):
+ """
+ POSTs a form that contains validation errors.
+ """
+ view_url = '/views/create_update/create/article/'
+ num_articles = Article.objects.count()
+ response = self.client.post(view_url, {
+ 'title': 'My First Article',
+ })
+ self.assertFormError(response, 'form', 'slug', [u'This field is required.'])
+ self.assertTemplateUsed(response, 'views/article_form.html')
+ self.assertEqual(num_articles, Article.objects.count(),
+ "Number of Articles should not have changed.")
+
+ def test_create_custom_save_article(self):
+ """
+ Creates a new article using a custom form class with a save method
+ that alters the slug entered.
+ """
+ view_url = '/views/create_update/create_custom/article/'
+ response = self.client.post(view_url, {
+ 'title': 'Test Article',
+ 'slug': 'this-should-get-replaced',
+ 'author': 1,
+ 'date_created': datetime.datetime(2007, 6, 25),
+ })
+ self.assertRedirects(response,
+ '/views/create_update/view/article/some-other-slug/',
+ target_status_code=404)
+
+class UpdateDeleteObjectTest(TestCase):
+
+ fixtures = ['testdata.json']
+
+ def test_update_object_form_display(self):
+ """
+ Verifies that the form was created properly and with initial values.
+ """
+ response = self.client.get('/views/create_update/update/article/old_article/')
+ self.assertTemplateUsed(response, 'views/article_form.html')
+ self.assertEquals(unicode(response.context['form']['title']),
+ u'<input id="id_title" type="text" name="title" value="Old Article" maxlength="100" />')
+
+ def test_update_object(self):
+ """
+ Verifies the updating of an Article.
+ """
+ response = self.client.post('/views/create_update/update/article/old_article/', {
+ 'title': 'Another Article',
+ 'slug': 'another-article-slug',
+ 'author': 1,
+ 'date_created': datetime.datetime(2007, 6, 25),
+ })
+ article = Article.objects.get(pk=1)
+ self.assertEquals(article.title, "Another Article")
+
+ def test_delete_object_confirm(self):
+ """
+ Verifies the confirm deletion page is displayed using a GET.
+ """
+ response = self.client.get('/views/create_update/delete/article/old_article/')
+ self.assertTemplateUsed(response, 'views/article_confirm_delete.html')
+
+ def test_delete_object(self):
+ """
+ Verifies the object actually gets deleted on a POST.
+ """
+ view_url = '/views/create_update/delete/article/old_article/'
+ response = self.client.post(view_url)
+ try:
+ Article.objects.get(slug='old_article')
+ except Article.DoesNotExist:
+ pass
+ else:
+ self.fail('Object was not deleted.')
+
+class PostSaveRedirectTests(TestCase):
+ """
+ Verifies that the views redirect to the correct locations depending on
+ if a post_save_redirect was passed and a get_absolute_url method exists
+ on the Model.
+ """
+
+ fixtures = ['testdata.json']
+ article_model = Article
+
+ create_url = '/views/create_update/create/article/'
+ update_url = '/views/create_update/update/article/old_article/'
+ delete_url = '/views/create_update/delete/article/old_article/'
+
+ create_redirect = '/views/create_update/view/article/my-first-article/'
+ update_redirect = '/views/create_update/view/article/another-article-slug/'
+ delete_redirect = '/views/create_update/'
+
+ def test_create_article(self):
+ num_articles = self.article_model.objects.count()
+ response = self.client.post(self.create_url, {
+ 'title': 'My First Article',
+ 'slug': 'my-first-article',
+ 'author': '1',
+ 'date_created': datetime.datetime(2007, 6, 25),
+ })
+ self.assertRedirects(response, self.create_redirect,
+ target_status_code=404)
+ self.assertEqual(num_articles + 1, self.article_model.objects.count(),
+ "A new Article should have been created.")
+
+ def test_update_article(self):
+ num_articles = self.article_model.objects.count()
+ response = self.client.post(self.update_url, {
+ 'title': 'Another Article',
+ 'slug': 'another-article-slug',
+ 'author': 1,
+ 'date_created': datetime.datetime(2007, 6, 25),
+ })
+ self.assertRedirects(response, self.update_redirect,
+ target_status_code=404)
+ self.assertEqual(num_articles, self.article_model.objects.count(),
+ "A new Article should not have been created.")
+
+ def test_delete_article(self):
+ num_articles = self.article_model.objects.count()
+ response = self.client.post(self.delete_url)
+ self.assertRedirects(response, self.delete_redirect,
+ target_status_code=404)
+ self.assertEqual(num_articles - 1, self.article_model.objects.count(),
+ "An Article should have been deleted.")
+
+class NoPostSaveNoAbsoluteUrl(PostSaveRedirectTests):
+ """
+ Tests that when no post_save_redirect is passed and no get_absolute_url
+ method exists on the Model that the view raises an ImproperlyConfigured
+ error.
+ """
+
+ create_url = '/views/create_update/no_redirect/create/article/'
+ update_url = '/views/create_update/no_redirect/update/article/old_article/'
+
+ def test_create_article(self):
+ self.assertRaises(ImproperlyConfigured,
+ super(NoPostSaveNoAbsoluteUrl, self).test_create_article)
+
+ def test_update_article(self):
+ self.assertRaises(ImproperlyConfigured,
+ super(NoPostSaveNoAbsoluteUrl, self).test_update_article)
+
+ def test_delete_article(self):
+ """
+ The delete_object view requires a post_delete_redirect, so skip testing
+ here.
+ """
+ pass
+
+class AbsoluteUrlNoPostSave(PostSaveRedirectTests):
+ """
+ Tests that the views redirect to the Model's get_absolute_url when no
+ post_save_redirect is passed.
+ """
+
+ # Article model with get_absolute_url method.
+ article_model = UrlArticle
+
+ create_url = '/views/create_update/no_url/create/article/'
+ update_url = '/views/create_update/no_url/update/article/old_article/'
+
+ create_redirect = '/urlarticles/my-first-article/'
+ update_redirect = '/urlarticles/another-article-slug/'
+
+ def test_delete_article(self):
+ """
+ The delete_object view requires a post_delete_redirect, so skip testing
+ here.
+ """
+ pass
diff --git a/parts/django/tests/regressiontests/views/tests/generic/date_based.py b/parts/django/tests/regressiontests/views/tests/generic/date_based.py
new file mode 100644
index 0000000..c6ba562
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/generic/date_based.py
@@ -0,0 +1,140 @@
+# coding: utf-8
+from django.test import TestCase
+from datetime import datetime, date
+from datetime import timedelta
+from regressiontests.views.models import Article, Author, DateArticle
+
+class ObjectDetailTest(TestCase):
+ fixtures = ['testdata.json']
+ def setUp(self):
+ # Correct the date for the current article
+ current_article = Article.objects.get(title="Current Article")
+ current_article.date_created = datetime.now()
+ current_article.save()
+
+ def test_finds_past(self):
+ "date_based.object_detail can view a page in the past"
+ response = self.client.get('/views/date_based/object_detail/2001/01/01/old_article/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['object'].title, "Old Article")
+
+ def test_object_detail_finds_today(self):
+ "date_based.object_detail can view a page from today"
+ today_url = datetime.now().strftime('%Y/%m/%d')
+ response = self.client.get('/views/date_based/object_detail/%s/current_article/' % today_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['object'].title, "Current Article")
+
+ def test_object_detail_ignores_future(self):
+ "date_based.object_detail can view a page from the future, but only if allowed."
+ response = self.client.get('/views/date_based/object_detail/3000/01/01/future_article/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_object_detail_allowed_future_if_enabled(self):
+ "date_based.object_detail can view a page from the future if explicitly allowed."
+ response = self.client.get('/views/date_based/object_detail/3000/01/01/future_article/allow_future/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['object'].title, "Future Article")
+
+class MonthArchiveTest(TestCase):
+ def test_archive_month_includes_only_month(self):
+ "Regression for #3031: Archives around Feburary include only one month"
+ author = Author(name="John Smith")
+ author.save()
+
+ # 2004 was a leap year, so it should be weird enough to not cheat
+ first_second_of_feb = datetime(2004, 2, 1, 0, 0, 1)
+ first_second_of_mar = datetime(2004, 3, 1, 0, 0, 1)
+ two_seconds = timedelta(0, 2, 0)
+ article = Article(title="example", author=author)
+
+ article.date_created = first_second_of_feb
+ article.save()
+ response = self.client.get('/views/date_based/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['next_month'], date(2004, 3, 1))
+ self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
+
+ article.date_created = first_second_of_feb-two_seconds
+ article.save()
+ response = self.client.get('/views/date_based/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 404)
+
+ article.date_created = first_second_of_mar-two_seconds
+ article.save()
+ response = self.client.get('/views/date_based/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['next_month'], date(2004, 3, 1))
+ self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
+
+ article.date_created = first_second_of_mar
+ article.save()
+ response = self.client.get('/views/date_based/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 404)
+
+ article2 = DateArticle(title="example", author=author)
+
+ article2.date_created = first_second_of_feb.date()
+ article2.save()
+ response = self.client.get('/views/date_based/datefield/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['next_month'], date(2004, 3, 1))
+ self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
+
+ article2.date_created = (first_second_of_feb-two_seconds).date()
+ article2.save()
+ response = self.client.get('/views/date_based/datefield/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 404)
+
+ article2.date_created = (first_second_of_mar-two_seconds).date()
+ article2.save()
+ response = self.client.get('/views/date_based/datefield/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['next_month'], date(2004, 3, 1))
+ self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
+
+ article2.date_created = first_second_of_mar.date()
+ article2.save()
+ response = self.client.get('/views/date_based/datefield/archive_month/2004/02/')
+ self.assertEqual(response.status_code, 404)
+
+ now = datetime.now()
+ prev_month = now.date().replace(day=1)
+ if prev_month.month == 1:
+ prev_month = prev_month.replace(year=prev_month.year-1, month=12)
+ else:
+ prev_month = prev_month.replace(month=prev_month.month-1)
+ article2.date_created = now
+ article2.save()
+ response = self.client.get('/views/date_based/datefield/archive_month/%s/' % now.strftime('%Y/%m'))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['next_month'], None)
+ self.assertEqual(response.context['previous_month'], prev_month)
+
+ def test_archive_month_date_list(self):
+ author = Author(name="John Smith")
+ author.save()
+ date1 = datetime(2010, 1, 1, 0, 0, 0)
+ date2 = datetime(2010, 1, 2, 0, 0, 0)
+ Article.objects.create(title='example1', author=author, date_created=date1)
+ Article.objects.create(title='example2', author=author, date_created=date2)
+ response = self.client.get('/views/date_based/archive_month/2010/1/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context['date_list']), 2)
+ self.assertEqual(response.context['date_list'][0], date1)
+ # Checks that the same date is not included more than once in the list
+ Article.objects.create(title='example2', author=author, date_created=date2)
+ response = self.client.get('/views/date_based/archive_month/2010/1/')
+ self.assertEqual(len(response.context['date_list']), 2)
+
+class DayArchiveTests(TestCase):
+
+ def test_year_month_day_format(self):
+ """
+ Make sure day views don't get confused with numeric month formats (#7944)
+ """
+ author = Author.objects.create(name="John Smith")
+ article = Article.objects.create(title="example", author=author, date_created=datetime(2004, 1, 21, 0, 0, 1))
+ response = self.client.get('/views/date_based/archive_day/2004/1/21/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['object_list'][0], article)
diff --git a/parts/django/tests/regressiontests/views/tests/i18n.py b/parts/django/tests/regressiontests/views/tests/i18n.py
new file mode 100644
index 0000000..24aa933
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/i18n.py
@@ -0,0 +1,149 @@
+# -*- coding:utf-8 -*-
+import gettext
+
+from django.conf import settings
+from django.test import TestCase
+from django.utils.translation import activate, deactivate
+from django.utils.text import javascript_quote
+
+from regressiontests.views.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"""
+ 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.assertEquals(self.client.session['django_language'], lang_code)
+
+ def test_jsi18n(self):
+ """The javascript_catalog can be deployed with language settings"""
+ for lang_code in ['es', 'fr', 'ru']:
+ activate(lang_code)
+ catalog = gettext.translation('djangojs', locale_dir, [lang_code])
+ 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)
+
+
+class JsI18NTests(TestCase):
+ """
+ Tests django views in django/views/i18n.py that need to change
+ settings.LANGUAGE_CODE.
+ """
+
+ def setUp(self):
+ self.old_language_code = settings.LANGUAGE_CODE
+ self.old_installed_apps = settings.INSTALLED_APPS
+
+ def tearDown(self):
+ deactivate()
+ settings.LANGUAGE_CODE = self.old_language_code
+ settings.INSTALLED_APPS = self.old_installed_apps
+
+ 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.
+ """
+ settings.LANGUAGE_CODE = 'es'
+ activate('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.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ activate('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.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ activate('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.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.views.app0']
+ activate('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.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ activate('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 setUp(self):
+ self.old_language_code = settings.LANGUAGE_CODE
+ self.old_installed_apps = settings.INSTALLED_APPS
+
+ def tearDown(self):
+ settings.LANGUAGE_CODE = self.old_language_code
+ settings.INSTALLED_APPS = self.old_installed_apps
+
+ 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.
+ """
+ settings.LANGUAGE_CODE = 'en-us'
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.views.app1', 'regressiontests.views.app2']
+ activate('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'))
+ deactivate()
+
+ def testI18NDifferentNonEnLangs(self):
+ """
+ Similar to above but with neither default or requested language being
+ English.
+ """
+ settings.LANGUAGE_CODE = 'fr'
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.views.app3', 'regressiontests.views.app4']
+ activate('es-ar')
+ response = self.client.get('/views/jsi18n_multi_packages2/')
+ self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))
+ deactivate()
diff --git a/parts/django/tests/regressiontests/views/tests/specials.py b/parts/django/tests/regressiontests/views/tests/specials.py
new file mode 100644
index 0000000..bcdffca
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/specials.py
@@ -0,0 +1,35 @@
+# coding: utf-8
+from django.test import TestCase
+
+class URLHandling(TestCase):
+ """
+ Tests for URL handling in views and responses.
+ """
+ redirect_target = "/views/%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(u'/views/中文/')
+ 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('/views/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('/views/permanent_nonascii_redirect/')
+ self.assertRedirects(response, self.redirect_target, status_code=301)
+
diff --git a/parts/django/tests/regressiontests/views/tests/static.py b/parts/django/tests/regressiontests/views/tests/static.py
new file mode 100644
index 0000000..de0bd51
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/tests/static.py
@@ -0,0 +1,77 @@
+import mimetypes
+from os import path
+
+from django.test import TestCase
+from django.http import HttpResponseNotModified
+from regressiontests.views.urls import media_dir
+
+class StaticTests(TestCase):
+ """Tests django views in django/views/static.py"""
+
+ 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/site_media/%s' % filename)
+ file_path = path.join(media_dir, filename)
+ self.assertEquals(open(file_path).read(), response.content)
+ self.assertEquals(len(response.content), int(response['Content-Length']))
+ self.assertEquals(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))
+
+ def test_unknown_mime_type(self):
+ response = self.client.get('/views/site_media/file.unknown')
+ self.assertEquals('application/octet-stream', response['Content-Type'])
+
+ def test_copes_with_empty_path_component(self):
+ file_name = 'file.txt'
+ response = self.client.get('/views/site_media//%s' % file_name)
+ file = open(path.join(media_dir, file_name))
+ self.assertEquals(file.read(), response.content)
+
+ def test_is_modified_since(self):
+ file_name = 'file.txt'
+ response = self.client.get(
+ '/views/site_media/%s' % file_name,
+ HTTP_IF_MODIFIED_SINCE='Thu, 1 Jan 1970 00:00:00 GMT')
+ file = open(path.join(media_dir, file_name))
+ self.assertEquals(file.read(), response.content)
+
+ def test_not_modified_since(self):
+ file_name = 'file.txt'
+ response = self.client.get(
+ '/views/site_media/%s' % file_name,
+ HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 UTC'
+ # 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/site_media/%s' % file_name,
+ HTTP_IF_MODIFIED_SINCE=invalid_date)
+ file = open(path.join(media_dir, file_name))
+ self.assertEquals(file.read(), response.content)
+ self.assertEquals(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/site_media/%s' % file_name,
+ HTTP_IF_MODIFIED_SINCE=invalid_date)
+ file = open(path.join(media_dir, file_name))
+ self.assertEquals(file.read(), response.content)
+ self.assertEquals(len(response.content),
+ int(response['Content-Length']))
diff --git a/parts/django/tests/regressiontests/views/urls.py b/parts/django/tests/regressiontests/views/urls.py
new file mode 100644
index 0000000..0ccb988
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/urls.py
@@ -0,0 +1,131 @@
+# coding: utf-8
+from os import path
+
+from django.conf.urls.defaults import *
+
+from models import *
+import views
+
+
+base_dir = path.dirname(path.abspath(__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'),
+}
+
+date_based_info_dict = {
+ 'queryset': Article.objects.all(),
+ 'date_field': 'date_created',
+ 'month_format': '%m',
+}
+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'^$', 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'),
+
+ # 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),
+
+ # Static views
+ (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
+
+ # Special URLs for particular regression cases.
+ url(u'^中文/$', 'regressiontests.views.views.redirect'),
+ url(u'^中文/target/$', 'regressiontests.views.views.index_page'),
+)
+
+# Date-based generic views.
+urlpatterns += patterns('django.views.generic.date_based',
+ (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
+ 'object_detail',
+ dict(slug_field='slug', **date_based_info_dict)),
+ (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/allow_future/$',
+ 'object_detail',
+ dict(allow_future=True, slug_field='slug', **date_based_info_dict)),
+ (r'^date_based/archive_day/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$',
+ 'archive_day',
+ numeric_days_info_dict),
+ (r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
+ 'archive_month',
+ date_based_info_dict),
+ (r'^date_based/datefield/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
+ 'archive_month',
+ date_based_datefield_info_dict),
+)
+
+# crud generic views.
+
+urlpatterns += patterns('django.views.generic.create_update',
+ (r'^create_update/member/create/article/$', 'create_object',
+ dict(login_required=True, model=Article)),
+ (r'^create_update/create/article/$', 'create_object',
+ dict(post_save_redirect='/views/create_update/view/article/%(slug)s/',
+ model=Article)),
+ (r'^create_update/update/article/(?P<slug>[-\w]+)/$', 'update_object',
+ dict(post_save_redirect='/views/create_update/view/article/%(slug)s/',
+ slug_field='slug', model=Article)),
+ (r'^create_update/create_custom/article/$', views.custom_create),
+ (r'^create_update/delete/article/(?P<slug>[-\w]+)/$', 'delete_object',
+ dict(post_delete_redirect='/views/create_update/', slug_field='slug',
+ model=Article)),
+
+ # No post_save_redirect and no get_absolute_url on model.
+ (r'^create_update/no_redirect/create/article/$', 'create_object',
+ dict(model=Article)),
+ (r'^create_update/no_redirect/update/article/(?P<slug>[-\w]+)/$',
+ 'update_object', dict(slug_field='slug', model=Article)),
+
+ # get_absolute_url on model, but no passed post_save_redirect.
+ (r'^create_update/no_url/create/article/$', 'create_object',
+ dict(model=UrlArticle)),
+ (r'^create_update/no_url/update/article/(?P<slug>[-\w]+)/$',
+ 'update_object', dict(slug_field='slug', model=UrlArticle)),
+)
+
+# a view that raises an exception for the debug view
+urlpatterns += patterns('',
+ (r'^raises/$', views.raises),
+ (r'^raises404/$', views.raises404),
+)
+
+# rediriects, both temporary and permanent, with non-ASCII targets
+urlpatterns += patterns('django.views.generic.simple',
+ ('^nonascii_redirect/$', 'redirect_to',
+ {'url': u'/views/中文/target/', 'permanent': False}),
+ ('^permanent_nonascii_redirect/$', 'redirect_to',
+ {'url': u'/views/中文/target/', 'permanent': True}),
+)
+
+urlpatterns += patterns('regressiontests.views.views',
+ url(r'view_exception/(?P<n>\d+)/$', 'view_exception', name='view_exception'),
+ url(r'template_exception/(?P<n>\d+)/$', 'template_exception', name='template_exception'),
+)
diff --git a/parts/django/tests/regressiontests/views/views.py b/parts/django/tests/regressiontests/views/views.py
new file mode 100644
index 0000000..445b4ed
--- /dev/null
+++ b/parts/django/tests/regressiontests/views/views.py
@@ -0,0 +1,59 @@
+import sys
+
+from django.http import HttpResponse, HttpResponseRedirect
+from django import forms
+from django.views.debug import technical_500_response
+from django.views.generic.create_update import create_object
+from django.core.urlresolvers import get_resolver
+from django.shortcuts import render_to_response
+
+from regressiontests.views import BrokenException, except_args
+
+from models import Article
+
+
+def index_page(request):
+ """Dummy index page"""
+ return HttpResponse('<html><body>Dummy page</body></html>')
+
+def custom_create(request):
+ """
+ Calls create_object generic view with a custom form class.
+ """
+ class SlugChangingArticleForm(forms.ModelForm):
+ """Custom form class to overwrite the slug."""
+
+ class Meta:
+ model = Article
+
+ def save(self, *args, **kwargs):
+ self.instance.slug = 'some-other-slug'
+ return super(SlugChangingArticleForm, self).save(*args, **kwargs)
+
+ return create_object(request,
+ post_save_redirect='/views/create_update/view/article/%(slug)s/',
+ form_class=SlugChangingArticleForm)
+
+def raises(request):
+ try:
+ raise Exception
+ except Exception:
+ return technical_500_response(request, *sys.exc_info())
+
+def raises404(request):
+ resolver = get_resolver(None)
+ resolver.resolve('')
+
+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)]})
+