diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2013-04-18 17:16:39 +0100 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2013-04-18 17:16:39 +0100 |
| commit | 7f3678dc4cd7146c49bac3fb8f5211f647636aa3 (patch) | |
| tree | fdd2a60ccd1a9a3162d89f970db44502e35a3b3f /tests | |
| parent | b62e82365ad56ca930f7abb1d1dbdf9ce5a7c7c3 (diff) | |
| parent | 93c1576f17f6c5ee73f94f8de007f3c33010dc81 (diff) | |
Merge branch 'master' into schema-alteration
Conflicts:
django/db/backends/__init__.py
django/db/backends/mysql/base.py
django/db/backends/oracle/base.py
django/db/backends/oracle/creation.py
django/db/backends/postgresql_psycopg2/base.py
django/db/backends/sqlite3/base.py
django/db/models/fields/related.py
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/.coveragerc | 2 | ||||
| -rw-r--r-- | tests/admin_changelist/__init__.py (renamed from tests/modeltests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_changelist/admin.py (renamed from tests/regressiontests/admin_changelist/admin.py) | 8 | ||||
| -rw-r--r-- | tests/admin_changelist/models.py (renamed from tests/regressiontests/admin_changelist/models.py) | 7 | ||||
| -rw-r--r-- | tests/admin_changelist/tests.py (renamed from tests/regressiontests/admin_changelist/tests.py) | 46 | ||||
| -rw-r--r-- | tests/admin_changelist/urls.py (renamed from tests/regressiontests/admin_changelist/urls.py) | 0 | ||||
| -rw-r--r-- | tests/admin_custom_urls/__init__.py (renamed from tests/modeltests/distinct_on_fields/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_custom_urls/fixtures/actions.json (renamed from tests/regressiontests/admin_custom_urls/fixtures/actions.json) | 0 | ||||
| -rw-r--r-- | tests/admin_custom_urls/fixtures/users.json (renamed from tests/regressiontests/admin_custom_urls/fixtures/users.json) | 0 | ||||
| -rw-r--r-- | tests/admin_custom_urls/models.py (renamed from tests/regressiontests/admin_custom_urls/models.py) | 45 | ||||
| -rw-r--r-- | tests/admin_custom_urls/tests.py (renamed from tests/regressiontests/admin_custom_urls/tests.py) | 80 | ||||
| -rw-r--r-- | tests/admin_custom_urls/urls.py (renamed from tests/regressiontests/admin_custom_urls/urls.py) | 0 | ||||
| -rw-r--r-- | tests/admin_filters/__init__.py (renamed from tests/modeltests/aggregation/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_filters/models.py (renamed from tests/regressiontests/admin_filters/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_filters/tests.py (renamed from tests/regressiontests/admin_filters/tests.py) | 64 | ||||
| -rw-r--r-- | tests/admin_inlines/__init__.py (renamed from tests/modeltests/basic/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_inlines/admin.py (renamed from tests/regressiontests/admin_inlines/admin.py) | 13 | ||||
| -rw-r--r-- | tests/admin_inlines/fixtures/admin-views-users.xml (renamed from tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_inlines/models.py (renamed from tests/regressiontests/admin_inlines/models.py) | 28 | ||||
| -rw-r--r-- | tests/admin_inlines/tests.py (renamed from tests/regressiontests/admin_inlines/tests.py) | 134 | ||||
| -rw-r--r-- | tests/admin_inlines/urls.py (renamed from tests/regressiontests/admin_inlines/urls.py) | 0 | ||||
| -rw-r--r-- | tests/admin_ordering/__init__.py (renamed from tests/modeltests/choices/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_ordering/models.py (renamed from tests/regressiontests/admin_ordering/models.py) | 1 | ||||
| -rw-r--r-- | tests/admin_ordering/tests.py (renamed from tests/regressiontests/admin_ordering/tests.py) | 68 | ||||
| -rw-r--r-- | tests/admin_registration/__init__.py (renamed from tests/modeltests/custom_columns/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_registration/models.py (renamed from tests/regressiontests/admin_registration/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_registration/tests.py (renamed from tests/regressiontests/admin_registration/tests.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/__init__.py (renamed from tests/modeltests/custom_managers/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_import/__init__.py (renamed from tests/modeltests/custom_methods/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/app_with_import/models.py (renamed from tests/regressiontests/admin_scripts/app_with_import/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/broken_app/__init__.py (renamed from tests/modeltests/custom_pk/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/broken_app/models.py (renamed from tests/regressiontests/admin_scripts/broken_app/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/complex_app/__init__.py (renamed from tests/modeltests/defer/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/complex_app/admin/__init__.py (renamed from tests/modeltests/delete/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/complex_app/admin/foo.py (renamed from tests/regressiontests/admin_scripts/complex_app/admin/foo.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/complex_app/models/__init__.py (renamed from tests/regressiontests/admin_scripts/complex_app/models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/complex_app/models/bar.py (renamed from tests/regressiontests/admin_scripts/complex_app/models/bar.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/complex_app/models/foo.py (renamed from tests/regressiontests/admin_scripts/complex_app/models/foo.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/app_template/__init__.py (renamed from tests/modeltests/empty/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/app_template/api.py (renamed from tests/regressiontests/admin_scripts/custom_templates/app_template/api.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/app_template/models.py (renamed from tests/regressiontests/admin_scripts/custom_templates/app_template/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template.tgz (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template.tgz) | bin | 164 -> 164 bytes | |||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/additional_dir/Procfile (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/Procfile) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/additional_dir/extra.py (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/additional_dir/requirements.txt (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/requirements.txt) | 0 | ||||
| -rwxr-xr-x | tests/admin_scripts/custom_templates/project_template/manage.py (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/manage.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/project_name/__init__.py (renamed from tests/modeltests/empty/no_models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/project_name/settings.py (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/settings.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico) | bin | 894 -> 894 bytes | |||
| -rw-r--r-- | tests/admin_scripts/management/__init__.py (renamed from tests/modeltests/expressions/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/management/commands/__init__.py (renamed from tests/modeltests/field_defaults/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/management/commands/app_command.py (renamed from tests/regressiontests/admin_scripts/management/commands/app_command.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/management/commands/base_command.py (renamed from tests/regressiontests/admin_scripts/management/commands/base_command.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/management/commands/custom_startproject.py (renamed from tests/regressiontests/admin_scripts/management/commands/custom_startproject.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/management/commands/label_command.py (renamed from tests/regressiontests/admin_scripts/management/commands/label_command.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/management/commands/noargs_command.py (renamed from tests/regressiontests/admin_scripts/management/commands/noargs_command.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/models.py (renamed from tests/regressiontests/admin_scripts/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/simple_app/__init__.py (renamed from tests/modeltests/field_subclassing/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/simple_app/models.py (renamed from tests/regressiontests/admin_scripts/simple_app/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_scripts/tests.py (renamed from tests/regressiontests/admin_scripts/tests.py) | 205 | ||||
| -rw-r--r-- | tests/admin_scripts/urls.py (renamed from tests/regressiontests/admin_scripts/urls.py) | 0 | ||||
| -rw-r--r-- | tests/admin_util/__init__.py (renamed from tests/modeltests/force_insert_update/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_util/models.py (renamed from tests/regressiontests/admin_util/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_util/tests.py (renamed from tests/regressiontests/admin_util/tests.py) | 22 | ||||
| -rw-r--r-- | tests/admin_validation/__init__.py (renamed from tests/modeltests/generic_relations/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_validation/models.py (renamed from tests/regressiontests/admin_validation/models.py) | 0 | ||||
| -rw-r--r-- | tests/admin_validation/tests.py (renamed from tests/regressiontests/admin_validation/tests.py) | 22 | ||||
| -rw-r--r-- | tests/admin_views/__init__.py (renamed from tests/modeltests/get_latest/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_views/admin.py (renamed from tests/regressiontests/admin_views/admin.py) | 76 | ||||
| -rw-r--r-- | tests/admin_views/customadmin.py (renamed from tests/regressiontests/admin_views/customadmin.py) | 4 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-actions.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-actions.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-books.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-books.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-colors.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-colors.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-fabrics.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-person.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-person.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-unicode.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/admin-views-users.xml (renamed from tests/regressiontests/admin_views/fixtures/admin-views-users.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/deleted-objects.xml (renamed from tests/regressiontests/admin_views/fixtures/deleted-objects.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/multiple-child-classes.json (renamed from tests/regressiontests/admin_views/fixtures/multiple-child-classes.json) | 0 | ||||
| -rw-r--r-- | tests/admin_views/fixtures/string-primary-key.xml (renamed from tests/regressiontests/admin_views/fixtures/string-primary-key.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_views/forms.py (renamed from tests/regressiontests/admin_views/forms.py) | 0 | ||||
| -rw-r--r-- | tests/admin_views/models.py (renamed from tests/regressiontests/admin_views/models.py) | 28 | ||||
| -rw-r--r-- | tests/admin_views/templates/custom_filter_template.html (renamed from tests/regressiontests/admin_views/templates/custom_filter_template.html) | 0 | ||||
| -rw-r--r-- | tests/admin_views/tests.py (renamed from tests/regressiontests/admin_views/tests.py) | 424 | ||||
| -rw-r--r-- | tests/admin_views/urls.py (renamed from tests/regressiontests/admin_views/urls.py) | 0 | ||||
| -rw-r--r-- | tests/admin_views/views.py (renamed from tests/regressiontests/admin_views/views.py) | 0 | ||||
| -rw-r--r-- | tests/admin_widgets/__init__.py (renamed from tests/modeltests/get_object_or_404/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/admin_widgets/fixtures/admin-widgets-users.xml (renamed from tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml) | 0 | ||||
| -rw-r--r-- | tests/admin_widgets/models.py (renamed from tests/regressiontests/admin_widgets/models.py) | 4 | ||||
| -rw-r--r-- | tests/admin_widgets/tests.py (renamed from tests/regressiontests/admin_widgets/tests.py) | 16 | ||||
| -rw-r--r-- | tests/admin_widgets/urls.py (renamed from tests/regressiontests/admin_widgets/urls.py) | 0 | ||||
| -rw-r--r-- | tests/admin_widgets/widgetadmin.py (renamed from tests/regressiontests/admin_widgets/widgetadmin.py) | 0 | ||||
| -rw-r--r-- | tests/aggregation/__init__.py (renamed from tests/modeltests/get_or_create/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/aggregation/fixtures/aggregation.json (renamed from tests/modeltests/aggregation/fixtures/aggregation.json) | 0 | ||||
| -rw-r--r-- | tests/aggregation/models.py (renamed from tests/modeltests/aggregation/models.py) | 0 | ||||
| -rw-r--r-- | tests/aggregation/tests.py (renamed from tests/modeltests/aggregation/tests.py) | 8 | ||||
| -rw-r--r-- | tests/aggregation_regress/__init__.py (renamed from tests/modeltests/invalid_models/invalid_models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/aggregation_regress/fixtures/aggregation_regress.json (renamed from tests/regressiontests/aggregation_regress/fixtures/aggregation_regress.json) | 0 | ||||
| -rw-r--r-- | tests/aggregation_regress/models.py (renamed from tests/regressiontests/aggregation_regress/models.py) | 18 | ||||
| -rw-r--r-- | tests/aggregation_regress/tests.py (renamed from tests/regressiontests/aggregation_regress/tests.py) | 139 | ||||
| -rw-r--r-- | tests/app_loading/__init__.py (renamed from tests/modeltests/known_related_objects/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/app_loading/eggs/brokenapp.egg (renamed from tests/regressiontests/app_loading/eggs/brokenapp.egg) | bin | 1605 -> 1605 bytes | |||
| -rw-r--r-- | tests/app_loading/eggs/modelapp.egg (renamed from tests/regressiontests/app_loading/eggs/modelapp.egg) | bin | 3347 -> 3347 bytes | |||
| -rw-r--r-- | tests/app_loading/eggs/nomodelapp.egg (renamed from tests/regressiontests/app_loading/eggs/nomodelapp.egg) | bin | 1211 -> 1211 bytes | |||
| -rw-r--r-- | tests/app_loading/eggs/omelet.egg (renamed from tests/regressiontests/app_loading/eggs/omelet.egg) | bin | 9020 -> 9020 bytes | |||
| -rw-r--r-- | tests/app_loading/models.py (renamed from tests/modeltests/invalid_models/models.py) | 0 | ||||
| -rw-r--r-- | tests/app_loading/not_installed/__init__.py (renamed from tests/modeltests/lookup/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/app_loading/not_installed/models.py (renamed from tests/regressiontests/app_loading/not_installed/models.py) | 0 | ||||
| -rw-r--r-- | tests/app_loading/tests.py (renamed from tests/regressiontests/app_loading/tests.py) | 0 | ||||
| -rw-r--r-- | tests/backends/__init__.py (renamed from tests/modeltests/m2m_and_m2o/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/backends/models.py (renamed from tests/regressiontests/backends/models.py) | 30 | ||||
| -rw-r--r-- | tests/backends/tests.py (renamed from tests/regressiontests/backends/tests.py) | 245 | ||||
| -rw-r--r-- | tests/base/__init__.py (renamed from tests/modeltests/m2m_intermediary/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/base/models.py | 23 | ||||
| -rw-r--r-- | tests/bash_completion/__init__.py (renamed from tests/modeltests/files/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/bash_completion/management/__init__.py (renamed from tests/modeltests/invalid_models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/bash_completion/management/commands/__init__.py (renamed from tests/modeltests/m2m_signals/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/bash_completion/management/commands/test_command.py (renamed from tests/regressiontests/bash_completion/management/commands/test_command.py) | 0 | ||||
| -rw-r--r-- | tests/bash_completion/models.py (renamed from tests/regressiontests/bash_completion/models.py) | 0 | ||||
| -rw-r--r-- | tests/bash_completion/tests.py (renamed from tests/regressiontests/bash_completion/tests.py) | 2 | ||||
| -rw-r--r-- | tests/basic/__init__.py (renamed from tests/modeltests/m2m_multiple/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/basic/models.py (renamed from tests/modeltests/basic/models.py) | 0 | ||||
| -rw-r--r-- | tests/basic/tests.py (renamed from tests/modeltests/basic/tests.py) | 100 | ||||
| -rw-r--r-- | tests/bug639/__init__.py (renamed from tests/modeltests/m2m_recursive/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/bug639/models.py (renamed from tests/regressiontests/bug639/models.py) | 0 | ||||
| -rw-r--r-- | tests/bug639/test.jpg (renamed from tests/regressiontests/bug639/test.jpg) | bin | 1780 -> 1780 bytes | |||
| -rw-r--r-- | tests/bug639/tests.py (renamed from tests/regressiontests/bug639/tests.py) | 0 | ||||
| -rw-r--r-- | tests/bug8245/__init__.py (renamed from tests/modeltests/m2o_recursive/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/bug8245/admin.py (renamed from tests/regressiontests/bug8245/admin.py) | 0 | ||||
| -rw-r--r-- | tests/bug8245/models.py (renamed from tests/regressiontests/bug8245/models.py) | 0 | ||||
| -rw-r--r-- | tests/bug8245/tests.py (renamed from tests/regressiontests/bug8245/tests.py) | 0 | ||||
| -rw-r--r-- | tests/builtin_server/__init__.py (renamed from tests/modeltests/many_to_many/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/builtin_server/models.py (renamed from tests/modeltests/validators/models.py) | 0 | ||||
| -rw-r--r-- | tests/builtin_server/tests.py | 102 | ||||
| -rw-r--r-- | tests/bulk_create/__init__.py (renamed from tests/modeltests/many_to_one/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/bulk_create/models.py (renamed from tests/regressiontests/bulk_create/models.py) | 0 | ||||
| -rw-r--r-- | tests/bulk_create/tests.py (renamed from tests/regressiontests/bulk_create/tests.py) | 0 | ||||
| -rw-r--r-- | tests/cache/__init__.py (renamed from tests/modeltests/many_to_one_null/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/cache/closeable_cache.py (renamed from tests/regressiontests/cache/closeable_cache.py) | 0 | ||||
| -rw-r--r-- | tests/cache/liberal_backend.py (renamed from tests/regressiontests/cache/liberal_backend.py) | 0 | ||||
| -rw-r--r-- | tests/cache/models.py (renamed from tests/regressiontests/cache/models.py) | 0 | ||||
| -rw-r--r-- | tests/cache/tests.py (renamed from tests/regressiontests/cache/tests.py) | 185 | ||||
| -rw-r--r-- | tests/choices/__init__.py (renamed from tests/modeltests/model_forms/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/choices/models.py (renamed from tests/modeltests/choices/models.py) | 0 | ||||
| -rw-r--r-- | tests/choices/tests.py (renamed from tests/modeltests/choices/tests.py) | 0 | ||||
| -rw-r--r-- | tests/commands_sql/__init__.py (renamed from tests/modeltests/model_formsets/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/commands_sql/models.py | 7 | ||||
| -rw-r--r-- | tests/commands_sql/tests.py | 52 | ||||
| -rw-r--r-- | tests/comment_tests/__init__.py (renamed from tests/modeltests/model_inheritance/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/custom_comments/__init__.py | 32 | ||||
| -rw-r--r-- | tests/comment_tests/custom_comments/forms.py (renamed from tests/regressiontests/comment_tests/custom_comments/forms.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/custom_comments/models.py (renamed from tests/regressiontests/comment_tests/custom_comments/models.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/custom_comments/views.py (renamed from tests/regressiontests/comment_tests/custom_comments/views.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/fixtures/comment_tests.json (renamed from tests/regressiontests/comment_tests/fixtures/comment_tests.json) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/fixtures/comment_utils.xml (renamed from tests/regressiontests/comment_tests/fixtures/comment_utils.xml) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/models.py (renamed from tests/regressiontests/comment_tests/models.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/tests/__init__.py (renamed from tests/regressiontests/comment_tests/tests/__init__.py) | 18 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_app_api.py (renamed from tests/regressiontests/comment_tests/tests/app_api_tests.py) | 12 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_comment_form.py (renamed from tests/regressiontests/comment_tests/tests/comment_form_tests.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_comment_utils_moderators.py (renamed from tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py) | 10 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_comment_view.py (renamed from tests/regressiontests/comment_tests/tests/comment_view_tests.py) | 34 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_feeds.py (renamed from tests/regressiontests/comment_tests/tests/feed_tests.py) | 2 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_models.py (renamed from tests/regressiontests/comment_tests/tests/model_tests.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_moderation_views.py (renamed from tests/regressiontests/comment_tests/tests/moderation_view_tests.py) | 36 | ||||
| -rw-r--r-- | tests/comment_tests/tests/test_templatetags.py (renamed from tests/regressiontests/comment_tests/tests/templatetag_tests.py) | 36 | ||||
| -rw-r--r-- | tests/comment_tests/urls.py (renamed from tests/regressiontests/comment_tests/urls.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/urls_admin.py (renamed from tests/regressiontests/comment_tests/urls_admin.py) | 0 | ||||
| -rw-r--r-- | tests/comment_tests/urls_default.py (renamed from tests/regressiontests/comment_tests/urls_default.py) | 0 | ||||
| -rw-r--r-- | tests/conditional_processing/__init__.py (renamed from tests/regressiontests/conditional_processing/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/conditional_processing/models.py (renamed from tests/regressiontests/app_loading/models.py) | 0 | ||||
| -rw-r--r-- | tests/conditional_processing/tests.py (renamed from tests/regressiontests/conditional_processing/models.py) | 2 | ||||
| -rw-r--r-- | tests/conditional_processing/urls.py (renamed from tests/regressiontests/conditional_processing/urls.py) | 0 | ||||
| -rw-r--r-- | tests/conditional_processing/views.py (renamed from tests/regressiontests/conditional_processing/views.py) | 2 | ||||
| -rw-r--r-- | tests/contenttypes_tests/__init__.py (renamed from tests/modeltests/model_inheritance_same_model_name/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/contenttypes_tests/fixtures/testdata.json | 47 | ||||
| -rw-r--r-- | tests/contenttypes_tests/models.py | 24 | ||||
| -rw-r--r-- | tests/contenttypes_tests/tests.py | 47 | ||||
| -rw-r--r-- | tests/contenttypes_tests/urls.py | 7 | ||||
| -rw-r--r-- | tests/context_processors/__init__.py (renamed from tests/modeltests/mutually_referential/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/context_processors/models.py (renamed from tests/regressiontests/context_processors/models.py) | 0 | ||||
| -rw-r--r-- | tests/context_processors/templates/context_processors/request_attrs.html (renamed from tests/regressiontests/context_processors/templates/context_processors/request_attrs.html) | 0 | ||||
| -rw-r--r-- | tests/context_processors/tests.py (renamed from tests/regressiontests/context_processors/tests.py) | 2 | ||||
| -rw-r--r-- | tests/context_processors/urls.py (renamed from tests/regressiontests/context_processors/urls.py) | 0 | ||||
| -rw-r--r-- | tests/context_processors/views.py (renamed from tests/regressiontests/context_processors/views.py) | 0 | ||||
| -rw-r--r-- | tests/createsuperuser/__init__.py (renamed from tests/modeltests/one_to_one/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/createsuperuser/models.py (renamed from tests/regressiontests/builtin_server/models.py) | 0 | ||||
| -rw-r--r-- | tests/createsuperuser/tests.py (renamed from tests/regressiontests/createsuperuser/tests.py) | 0 | ||||
| -rw-r--r-- | tests/csrf_tests/__init__.py (renamed from tests/modeltests/or_lookups/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/csrf_tests/models.py (renamed from tests/regressiontests/csrf_tests/models.py) | 0 | ||||
| -rw-r--r-- | tests/csrf_tests/tests.py (renamed from tests/regressiontests/csrf_tests/tests.py) | 8 | ||||
| -rw-r--r-- | tests/custom_columns/__init__.py (renamed from tests/modeltests/order_with_respect_to/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/custom_columns/models.py (renamed from tests/modeltests/custom_columns/models.py) | 0 | ||||
| -rw-r--r-- | tests/custom_columns/tests.py (renamed from tests/modeltests/custom_columns/tests.py) | 49 | ||||
| -rw-r--r-- | tests/custom_columns_regress/__init__.py (renamed from tests/modeltests/ordering/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/custom_columns_regress/models.py (renamed from tests/regressiontests/custom_columns_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/custom_columns_regress/tests.py (renamed from tests/regressiontests/custom_columns_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/custom_managers/__init__.py (renamed from tests/modeltests/prefetch_related/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/custom_managers/models.py (renamed from tests/modeltests/custom_managers/models.py) | 10 | ||||
| -rw-r--r-- | tests/custom_managers/tests.py (renamed from tests/modeltests/custom_managers/tests.py) | 0 | ||||
| -rw-r--r-- | tests/custom_managers_regress/__init__.py (renamed from tests/modeltests/properties/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/custom_managers_regress/models.py (renamed from tests/regressiontests/custom_managers_regress/models.py) | 4 | ||||
| -rw-r--r-- | tests/custom_managers_regress/tests.py (renamed from tests/regressiontests/custom_managers_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/custom_methods/__init__.py (renamed from tests/modeltests/proxy_model_inheritance/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/custom_methods/models.py (renamed from tests/modeltests/custom_methods/models.py) | 0 | ||||
| -rw-r--r-- | tests/custom_methods/tests.py (renamed from tests/modeltests/custom_methods/tests.py) | 0 | ||||
| -rw-r--r-- | tests/custom_pk/__init__.py (renamed from tests/modeltests/proxy_model_inheritance/app1/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/custom_pk/fields.py (renamed from tests/modeltests/custom_pk/fields.py) | 0 | ||||
| -rw-r--r-- | tests/custom_pk/models.py (renamed from tests/modeltests/custom_pk/models.py) | 0 | ||||
| -rw-r--r-- | tests/custom_pk/tests.py (renamed from tests/modeltests/custom_pk/tests.py) | 0 | ||||
| -rw-r--r-- | tests/datatypes/__init__.py (renamed from tests/modeltests/proxy_model_inheritance/app2/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/datatypes/models.py (renamed from tests/regressiontests/datatypes/models.py) | 0 | ||||
| -rw-r--r-- | tests/datatypes/tests.py (renamed from tests/regressiontests/datatypes/tests.py) | 0 | ||||
| -rw-r--r-- | tests/dates/__init__.py (renamed from tests/modeltests/proxy_models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/dates/models.py (renamed from tests/regressiontests/dates/models.py) | 2 | ||||
| -rw-r--r-- | tests/dates/tests.py (renamed from tests/regressiontests/dates/tests.py) | 38 | ||||
| -rw-r--r-- | tests/datetimes/__init__.py (renamed from tests/modeltests/raw_query/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/datetimes/models.py | 28 | ||||
| -rw-r--r-- | tests/datetimes/tests.py | 83 | ||||
| -rw-r--r-- | tests/db_typecasts/__init__.py (renamed from tests/modeltests/reserved_names/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/db_typecasts/models.py (renamed from tests/regressiontests/createsuperuser/models.py) | 0 | ||||
| -rw-r--r-- | tests/db_typecasts/tests.py (renamed from tests/regressiontests/db_typecasts/tests.py) | 0 | ||||
| -rw-r--r-- | tests/decorators/__init__.py (renamed from tests/modeltests/reverse_lookup/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/decorators/models.py (renamed from tests/regressiontests/decorators/models.py) | 0 | ||||
| -rw-r--r-- | tests/decorators/tests.py (renamed from tests/regressiontests/decorators/tests.py) | 17 | ||||
| -rw-r--r-- | tests/defaultfilters/__init__.py (renamed from tests/modeltests/save_delete_hooks/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/defaultfilters/models.py (renamed from tests/regressiontests/db_typecasts/models.py) | 0 | ||||
| -rw-r--r-- | tests/defaultfilters/tests.py (renamed from tests/regressiontests/defaultfilters/tests.py) | 5 | ||||
| -rw-r--r-- | tests/defer/__init__.py (renamed from tests/modeltests/schema/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/defer/models.py (renamed from tests/modeltests/defer/models.py) | 0 | ||||
| -rw-r--r-- | tests/defer/tests.py (renamed from tests/modeltests/defer/tests.py) | 0 | ||||
| -rw-r--r-- | tests/defer_regress/__init__.py (renamed from tests/modeltests/select_related/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/defer_regress/models.py (renamed from tests/regressiontests/defer_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/defer_regress/tests.py (renamed from tests/regressiontests/defer_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/delete/__init__.py (renamed from tests/modeltests/serializers/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/delete/models.py (renamed from tests/modeltests/delete/models.py) | 0 | ||||
| -rw-r--r-- | tests/delete/tests.py (renamed from tests/modeltests/delete/tests.py) | 23 | ||||
| -rw-r--r-- | tests/delete_regress/__init__.py (renamed from tests/modeltests/model_package/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/delete_regress/models.py (renamed from tests/regressiontests/delete_regress/models.py) | 10 | ||||
| -rw-r--r-- | tests/delete_regress/tests.py (renamed from tests/regressiontests/delete_regress/tests.py) | 19 | ||||
| -rw-r--r-- | tests/deprecation/__init__.py (renamed from tests/modeltests/signals/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/deprecation/models.py (renamed from tests/regressiontests/defaultfilters/models.py) | 0 | ||||
| -rw-r--r-- | tests/deprecation/tests.py | 158 | ||||
| -rw-r--r-- | tests/dispatch/__init__.py (renamed from tests/regressiontests/dispatch/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/dispatch/models.py (renamed from tests/regressiontests/dispatch/models.py) | 0 | ||||
| -rw-r--r-- | tests/dispatch/tests/__init__.py (renamed from tests/regressiontests/dispatch/tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/dispatch/tests/test_dispatcher.py (renamed from tests/regressiontests/dispatch/tests/test_dispatcher.py) | 0 | ||||
| -rw-r--r-- | tests/dispatch/tests/test_saferef.py (renamed from tests/regressiontests/dispatch/tests/test_saferef.py) | 0 | ||||
| -rw-r--r-- | tests/distinct_on_fields/__init__.py (renamed from tests/modeltests/select_for_update/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/distinct_on_fields/models.py (renamed from tests/modeltests/distinct_on_fields/models.py) | 0 | ||||
| -rw-r--r-- | tests/distinct_on_fields/tests.py (renamed from tests/modeltests/distinct_on_fields/tests.py) | 0 | ||||
| -rw-r--r-- | tests/empty/__init__.py (renamed from tests/modeltests/str/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/empty/models.py (renamed from tests/modeltests/empty/models.py) | 0 | ||||
| -rw-r--r-- | tests/empty/no_models/__init__.py (renamed from tests/modeltests/tablespaces/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/empty/no_models/tests.py | 6 | ||||
| -rw-r--r-- | tests/empty/tests.py (renamed from tests/modeltests/empty/tests.py) | 2 | ||||
| -rw-r--r-- | tests/expressions/__init__.py (renamed from tests/modeltests/test_client/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/expressions/models.py (renamed from tests/modeltests/expressions/models.py) | 0 | ||||
| -rw-r--r-- | tests/expressions/tests.py (renamed from tests/modeltests/expressions/tests.py) | 0 | ||||
| -rw-r--r-- | tests/expressions_regress/__init__.py (renamed from tests/modeltests/timezones/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/expressions_regress/models.py (renamed from tests/regressiontests/expressions_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/expressions_regress/tests.py (renamed from tests/regressiontests/expressions_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/extra_regress/__init__.py (renamed from tests/modeltests/transactions/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/extra_regress/models.py (renamed from tests/regressiontests/extra_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/extra_regress/tests.py (renamed from tests/regressiontests/extra_regress/tests.py) | 5 | ||||
| -rw-r--r-- | tests/field_defaults/__init__.py (renamed from tests/modeltests/update/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/field_defaults/models.py (renamed from tests/modeltests/field_defaults/models.py) | 0 | ||||
| -rw-r--r-- | tests/field_defaults/tests.py (renamed from tests/modeltests/field_defaults/tests.py) | 0 | ||||
| -rw-r--r-- | tests/field_subclassing/__init__.py (renamed from tests/modeltests/update_only_fields/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/field_subclassing/fields.py (renamed from tests/modeltests/field_subclassing/fields.py) | 0 | ||||
| -rw-r--r-- | tests/field_subclassing/models.py (renamed from tests/modeltests/field_subclassing/models.py) | 0 | ||||
| -rw-r--r-- | tests/field_subclassing/tests.py (renamed from tests/modeltests/field_subclassing/tests.py) | 6 | ||||
| -rw-r--r-- | tests/file_storage/__init__.py (renamed from tests/regressiontests/bash_completion/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/file_storage/magic.png | bin | 0 -> 70445 bytes | |||
| -rw-r--r-- | tests/file_storage/models.py (renamed from tests/regressiontests/file_storage/models.py) | 0 | ||||
| -rw-r--r-- | tests/file_storage/test.png (renamed from tests/modeltests/model_forms/test.png) | bin | 482 -> 482 bytes | |||
| -rw-r--r-- | tests/file_storage/test1.png (renamed from tests/regressiontests/file_storage/test1.png) | bin | 480 -> 480 bytes | |||
| -rw-r--r-- | tests/file_storage/tests.py (renamed from tests/regressiontests/file_storage/tests.py) | 45 | ||||
| -rw-r--r-- | tests/file_uploads/__init__.py (renamed from tests/modeltests/user_commands/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/file_uploads/models.py (renamed from tests/regressiontests/file_uploads/models.py) | 0 | ||||
| -rw-r--r-- | tests/file_uploads/tests.py (renamed from tests/regressiontests/file_uploads/tests.py) | 0 | ||||
| -rw-r--r-- | tests/file_uploads/uploadhandler.py (renamed from tests/regressiontests/file_uploads/uploadhandler.py) | 0 | ||||
| -rw-r--r-- | tests/file_uploads/urls.py (renamed from tests/regressiontests/file_uploads/urls.py) | 0 | ||||
| -rw-r--r-- | tests/file_uploads/views.py (renamed from tests/regressiontests/file_uploads/views.py) | 0 | ||||
| -rw-r--r-- | tests/files/__init__.py (renamed from tests/regressiontests/bash_completion/management/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/files/models.py (renamed from tests/modeltests/files/models.py) | 2 | ||||
| -rw-r--r-- | tests/files/tests.py (renamed from tests/modeltests/files/tests.py) | 42 | ||||
| -rw-r--r-- | tests/fixtures/__init__.py (renamed from tests/modeltests/fixtures/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/db_fixture_1.default.json (renamed from tests/modeltests/fixtures/fixtures/db_fixture_1.default.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/db_fixture_2.default.json.gz (renamed from tests/modeltests/fixtures/fixtures/db_fixture_2.default.json.gz) | bin | 175 -> 175 bytes | |||
| -rw-r--r-- | tests/fixtures/fixtures/db_fixture_3.nosuchdb.json (renamed from tests/modeltests/fixtures/fixtures/db_fixture_3.nosuchdb.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture1.json (renamed from tests/modeltests/fixtures/fixtures/fixture1.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture2.json (renamed from tests/modeltests/fixtures/fixtures/fixture2.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture2.xml (renamed from tests/modeltests/fixtures/fixtures/fixture2.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture3.xml (renamed from tests/modeltests/fixtures/fixtures/fixture3.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture4.json.zip (renamed from tests/modeltests/fixtures/fixtures/fixture4.json.zip) | bin | 282 -> 282 bytes | |||
| -rw-r--r-- | tests/fixtures/fixtures/fixture5.json.gz (renamed from tests/modeltests/fixtures/fixtures/fixture5.json.gz) | bin | 169 -> 169 bytes | |||
| -rw-r--r-- | tests/fixtures/fixtures/fixture5.json.zip (renamed from tests/modeltests/fixtures/fixtures/fixture5.json.zip) | bin | 295 -> 295 bytes | |||
| -rw-r--r-- | tests/fixtures/fixtures/fixture6.json (renamed from tests/modeltests/fixtures/fixtures/fixture6.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture7.xml (renamed from tests/modeltests/fixtures/fixtures/fixture7.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture8.json (renamed from tests/modeltests/fixtures/fixtures/fixture8.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/fixture9.xml (renamed from tests/modeltests/fixtures/fixtures/fixture9.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/initial_data.json (renamed from tests/modeltests/fixtures/fixtures/initial_data.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/fixtures/invalid.json (renamed from tests/modeltests/fixtures/fixtures/invalid.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures/models.py (renamed from tests/modeltests/fixtures/models.py) | 4 | ||||
| -rw-r--r-- | tests/fixtures/tests.py (renamed from tests/modeltests/fixtures/tests.py) | 19 | ||||
| -rw-r--r-- | tests/fixtures_model_package/__init__.py (renamed from tests/modeltests/fixtures_model_package/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/fixtures_model_package/fixtures/fixture1.json (renamed from tests/modeltests/fixtures_model_package/fixtures/fixture1.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_model_package/fixtures/fixture2.json (renamed from tests/modeltests/fixtures_model_package/fixtures/fixture2.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_model_package/fixtures/fixture2.xml (renamed from tests/modeltests/fixtures_model_package/fixtures/fixture2.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures_model_package/fixtures/initial_data.json (renamed from tests/modeltests/fixtures_model_package/fixtures/initial_data.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_model_package/models/__init__.py (renamed from tests/modeltests/fixtures_model_package/models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/fixtures_model_package/tests.py (renamed from tests/modeltests/fixtures_model_package/tests.py) | 11 | ||||
| -rw-r--r-- | tests/fixtures_regress/__init__.py (renamed from tests/modeltests/user_commands/management/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/absolute.json (renamed from tests/regressiontests/fixtures_regress/fixtures/absolute.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/animal.xml (renamed from tests/regressiontests/fixtures_regress/fixtures/animal.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/bad_fixture1.unkn (renamed from tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/bad_fixture2.xml (renamed from tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/big-fixture.json (renamed from tests/regressiontests/fixtures_regress/fixtures/big-fixture.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/empty.json (renamed from tests/regressiontests/fixtures_regress/fixtures/empty.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/forward_ref.json (renamed from tests/regressiontests/fixtures_regress/fixtures/forward_ref.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/forward_ref_bad_data.json (renamed from tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/forward_ref_lookup.json (renamed from tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/model-inheritance.json (renamed from tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/nk-inheritance.json (renamed from tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/nk-inheritance2.xml (renamed from tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/non_natural_1.json (renamed from tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/non_natural_2.xml (renamed from tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/pretty.xml (renamed from tests/regressiontests/fixtures_regress/fixtures/pretty.xml) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/sequence.json (renamed from tests/regressiontests/fixtures_regress/fixtures/sequence.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/sequence_extra.json (renamed from tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/sequence_extra_xml.xml | 10 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures/thingy.json (renamed from tests/regressiontests/fixtures_regress/fixtures/thingy.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures_1/forward_ref_1.json (renamed from tests/regressiontests/fixtures_regress/fixtures_1/forward_ref_1.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/fixtures_2/forward_ref_2.json (renamed from tests/regressiontests/fixtures_regress/fixtures_2/forward_ref_2.json) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/models.py (renamed from tests/regressiontests/fixtures_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/fixtures_regress/tests.py (renamed from tests/regressiontests/fixtures_regress/tests.py) | 44 | ||||
| -rw-r--r-- | tests/force_insert_update/__init__.py (renamed from tests/modeltests/user_commands/management/commands/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/force_insert_update/models.py (renamed from tests/modeltests/force_insert_update/models.py) | 0 | ||||
| -rw-r--r-- | tests/force_insert_update/tests.py (renamed from tests/modeltests/force_insert_update/tests.py) | 0 | ||||
| -rw-r--r-- | tests/foreign_object/__init__.py (renamed from tests/modeltests/validators/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/foreign_object/models.py | 152 | ||||
| -rw-r--r-- | tests/foreign_object/tests.py | 338 | ||||
| -rw-r--r-- | tests/forms_tests/__init__.py (renamed from tests/regressiontests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/models.py (renamed from tests/regressiontests/forms/models.py) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/templates/forms_tests/article_form.html (renamed from tests/regressiontests/forms/templates/forms/article_form.html) | 2 | ||||
| -rw-r--r-- | tests/forms_tests/tests/__init__.py | 21 | ||||
| -rw-r--r-- | tests/forms_tests/tests/filepath_test_files/.dot-file (renamed from tests/regressiontests/forms/tests/filepath_test_files/.dot-file) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/tests/filepath_test_files/directory/.keep (renamed from tests/regressiontests/forms/tests/filepath_test_files/directory/.keep) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/tests/filepath_test_files/fake-image.jpg (renamed from tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/tests/filepath_test_files/real-text-file.txt (renamed from tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_error_messages.py (renamed from tests/regressiontests/forms/tests/error_messages.py) | 10 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_extra.py (renamed from tests/regressiontests/forms/tests/extra.py) | 85 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_fields.py (renamed from tests/regressiontests/forms/tests/fields.py) | 194 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_forms.py (renamed from tests/regressiontests/forms/tests/forms.py) | 165 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_formsets.py (renamed from tests/regressiontests/forms/tests/formsets.py) | 203 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_input_formats.py (renamed from tests/regressiontests/forms/tests/input_formats.py) | 7 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_media.py (renamed from tests/regressiontests/forms/tests/media.py) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_regressions.py (renamed from tests/regressiontests/forms/tests/regressions.py) | 15 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_util.py (renamed from tests/regressiontests/forms/tests/util.py) | 0 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_validators.py | 39 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_widgets.py (renamed from tests/regressiontests/forms/tests/widgets.py) | 138 | ||||
| -rw-r--r-- | tests/forms_tests/tests/tests.py (renamed from tests/regressiontests/forms/tests/models.py) | 3 | ||||
| -rw-r--r-- | tests/forms_tests/urls.py (renamed from tests/regressiontests/forms/urls.py) | 2 | ||||
| -rw-r--r-- | tests/forms_tests/views.py (renamed from tests/regressiontests/forms/views.py) | 0 | ||||
| -rw-r--r-- | tests/generic_inline_admin/__init__.py (renamed from tests/regressiontests/admin_changelist/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/generic_inline_admin/admin.py (renamed from tests/regressiontests/generic_inline_admin/admin.py) | 0 | ||||
| -rw-r--r-- | tests/generic_inline_admin/fixtures/users.xml (renamed from tests/regressiontests/generic_inline_admin/fixtures/users.xml) | 0 | ||||
| -rw-r--r-- | tests/generic_inline_admin/models.py (renamed from tests/regressiontests/generic_inline_admin/models.py) | 0 | ||||
| -rw-r--r-- | tests/generic_inline_admin/tests.py (renamed from tests/regressiontests/generic_inline_admin/tests.py) | 29 | ||||
| -rw-r--r-- | tests/generic_inline_admin/urls.py (renamed from tests/regressiontests/generic_inline_admin/urls.py) | 0 | ||||
| -rw-r--r-- | tests/generic_relations/__init__.py (renamed from tests/regressiontests/admin_filters/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/generic_relations/models.py (renamed from tests/modeltests/generic_relations/models.py) | 8 | ||||
| -rw-r--r-- | tests/generic_relations/tests.py (renamed from tests/modeltests/generic_relations/tests.py) | 12 | ||||
| -rw-r--r-- | tests/generic_relations_regress/__init__.py (renamed from tests/regressiontests/admin_inlines/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/generic_relations_regress/models.py (renamed from tests/regressiontests/generic_relations_regress/models.py) | 40 | ||||
| -rw-r--r-- | tests/generic_relations_regress/tests.py (renamed from tests/regressiontests/generic_relations_regress/tests.py) | 65 | ||||
| -rw-r--r-- | tests/generic_views/__init__.py (renamed from tests/regressiontests/admin_ordering/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/generic_views/fixtures/generic-views-test-data.json (renamed from tests/regressiontests/generic_views/fixtures/generic-views-test-data.json) | 0 | ||||
| -rw-r--r-- | tests/generic_views/models.py (renamed from tests/regressiontests/generic_views/models.py) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/about.html (renamed from tests/regressiontests/generic_views/templates/generic_views/about.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/apple_detail.html (renamed from tests/regressiontests/generic_views/templates/generic_views/apple_detail.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/artist_detail.html (renamed from tests/regressiontests/generic_views/templates/generic_views/artist_detail.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/artist_form.html (renamed from tests/regressiontests/generic_views/templates/generic_views/artist_form.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/author_confirm_delete.html (renamed from tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/author_detail.html (renamed from tests/regressiontests/generic_views/templates/generic_views/author_detail.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/author_form.html (renamed from tests/regressiontests/generic_views/templates/generic_views/author_form.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/author_list.html (renamed from tests/regressiontests/generic_views/templates/generic_views/author_list.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/author_objects.html (renamed from tests/regressiontests/generic_views/templates/generic_views/author_objects.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/author_view.html (renamed from tests/regressiontests/generic_views/templates/generic_views/author_view.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_archive.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_archive.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_archive_day.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_archive_month.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_archive_week.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_archive_year.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_detail.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_detail.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/book_list.html (renamed from tests/regressiontests/generic_views/templates/generic_views/book_list.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/confirm_delete.html (renamed from tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/detail.html (renamed from tests/regressiontests/generic_views/templates/generic_views/detail.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/form.html (renamed from tests/regressiontests/generic_views/templates/generic_views/form.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/list.html (renamed from tests/regressiontests/generic_views/templates/generic_views/list.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/page_template.html (renamed from tests/regressiontests/generic_views/templates/generic_views/page_template.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/templates/generic_views/robots.txt | 1 | ||||
| -rw-r--r-- | tests/generic_views/templates/registration/login.html (renamed from tests/regressiontests/generic_views/templates/registration/login.html) | 0 | ||||
| -rw-r--r-- | tests/generic_views/test_base.py (renamed from tests/regressiontests/generic_views/base.py) | 29 | ||||
| -rw-r--r-- | tests/generic_views/test_dates.py (renamed from tests/regressiontests/generic_views/dates.py) | 27 | ||||
| -rw-r--r-- | tests/generic_views/test_detail.py (renamed from tests/regressiontests/generic_views/detail.py) | 2 | ||||
| -rw-r--r-- | tests/generic_views/test_edit.py (renamed from tests/regressiontests/generic_views/edit.py) | 27 | ||||
| -rw-r--r-- | tests/generic_views/test_forms.py (renamed from tests/regressiontests/generic_views/forms.py) | 0 | ||||
| -rw-r--r-- | tests/generic_views/test_list.py (renamed from tests/regressiontests/generic_views/list.py) | 2 | ||||
| -rw-r--r-- | tests/generic_views/tests.py | 11 | ||||
| -rw-r--r-- | tests/generic_views/urls.py (renamed from tests/regressiontests/generic_views/urls.py) | 4 | ||||
| -rw-r--r-- | tests/generic_views/views.py (renamed from tests/regressiontests/generic_views/views.py) | 3 | ||||
| -rw-r--r-- | tests/get_earliest_or_latest/__init__.py (renamed from tests/regressiontests/admin_registration/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/get_earliest_or_latest/models.py | 16 | ||||
| -rw-r--r-- | tests/get_earliest_or_latest/tests.py | 123 | ||||
| -rw-r--r-- | tests/get_object_or_404/__init__.py (renamed from tests/regressiontests/admin_scripts/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/get_object_or_404/models.py (renamed from tests/modeltests/get_object_or_404/models.py) | 4 | ||||
| -rw-r--r-- | tests/get_object_or_404/tests.py (renamed from tests/modeltests/get_object_or_404/tests.py) | 2 | ||||
| -rw-r--r-- | tests/get_or_create/__init__.py (renamed from tests/regressiontests/admin_scripts/app_with_import/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/get_or_create/models.py (renamed from tests/modeltests/get_or_create/models.py) | 0 | ||||
| -rw-r--r-- | tests/get_or_create/tests.py (renamed from tests/modeltests/get_or_create/tests.py) | 0 | ||||
| -rw-r--r-- | tests/get_or_create_regress/__init__.py (renamed from tests/regressiontests/admin_scripts/broken_app/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/get_or_create_regress/models.py (renamed from tests/regressiontests/get_or_create_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/get_or_create_regress/tests.py (renamed from tests/regressiontests/get_or_create_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/handlers/__init__.py (renamed from tests/regressiontests/admin_scripts/complex_app/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/handlers/models.py (renamed from tests/regressiontests/handlers/models.py) | 0 | ||||
| -rw-r--r-- | tests/handlers/tests.py | 91 | ||||
| -rw-r--r-- | tests/handlers/urls.py | 12 | ||||
| -rw-r--r-- | tests/handlers/views.py | 17 | ||||
| -rw-r--r-- | tests/http_utils/__init__.py (renamed from tests/regressiontests/admin_scripts/complex_app/admin/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/http_utils/models.py (renamed from tests/regressiontests/http_utils/models.py) | 0 | ||||
| -rw-r--r-- | tests/http_utils/tests.py (renamed from tests/regressiontests/http_utils/tests.py) | 0 | ||||
| -rw-r--r-- | tests/httpwrappers/__init__.py (renamed from tests/regressiontests/admin_scripts/custom_templates/app_template/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/httpwrappers/abc.txt (renamed from tests/regressiontests/httpwrappers/abc.txt) | 0 | ||||
| -rw-r--r-- | tests/httpwrappers/models.py (renamed from tests/regressiontests/httpwrappers/models.py) | 0 | ||||
| -rw-r--r-- | tests/httpwrappers/tests.py (renamed from tests/regressiontests/httpwrappers/tests.py) | 46 | ||||
| -rw-r--r-- | tests/i18n/__init__.py (renamed from tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/__init__.py (renamed from tests/regressiontests/i18n/commands/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/code.sample (renamed from tests/regressiontests/i18n/commands/code.sample) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/compilation.py (renamed from tests/regressiontests/i18n/commands/compilation.py) | 65 | ||||
| -rw-r--r-- | tests/i18n/commands/extraction.py (renamed from tests/regressiontests/i18n/commands/extraction.py) | 166 | ||||
| -rw-r--r-- | tests/i18n/commands/ignore_dir/ignored.html (renamed from tests/regressiontests/i18n/commands/ignore_dir/ignored.html) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/javascript.js (renamed from tests/regressiontests/i18n/commands/javascript.js) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/locale/es_AR/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/commands/locale/es_AR/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/locale/fr/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/commands/locale/fr/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/locale/hr/LC_MESSAGES/django.po | 71 | ||||
| -rw-r--r-- | tests/i18n/commands/locale/it/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/locale/ja/LC_MESSAGES/django.po | 21 | ||||
| -rw-r--r-- | tests/i18n/commands/templates/comments.thtml | 13 | ||||
| -rw-r--r-- | tests/i18n/commands/templates/template_with_error.tpl (renamed from tests/regressiontests/i18n/commands/templates/template_with_error.tpl) | 0 | ||||
| -rw-r--r-- | tests/i18n/commands/templates/test.html (renamed from tests/regressiontests/i18n/commands/templates/test.html) | 2 | ||||
| -rw-r--r-- | tests/i18n/commands/tests.py | 24 | ||||
| -rw-r--r-- | tests/i18n/contenttypes/__init__.py (renamed from tests/regressiontests/admin_scripts/management/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/contenttypes/locale/en/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/contenttypes/locale/en/LC_MESSAGES/django.mo) | bin | 418 -> 418 bytes | |||
| -rw-r--r-- | tests/i18n/contenttypes/locale/en/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/contenttypes/locale/en/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.mo) | bin | 461 -> 461 bytes | |||
| -rw-r--r-- | tests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/contenttypes/tests.py (renamed from tests/regressiontests/i18n/contenttypes/tests.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/forms.py (renamed from tests/regressiontests/i18n/forms.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/models.py (renamed from tests/regressiontests/i18n/models.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/other/__init__.py (renamed from tests/regressiontests/admin_scripts/management/commands/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/other/locale/__init__.py (renamed from tests/regressiontests/admin_scripts/simple_app/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/other/locale/de/LC_MESSAGES/django.mo | bin | 0 -> 2070 bytes | |||
| -rw-r--r-- | tests/i18n/other/locale/de/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po) | 32 | ||||
| -rw-r--r-- | tests/i18n/other/locale/de/__init__.py (renamed from tests/regressiontests/admin_util/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/other/locale/de/formats.py (renamed from tests/regressiontests/i18n/other/locale/de/formats.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/other/locale/fr/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.mo) | bin | 528 -> 528 bytes | |||
| -rw-r--r-- | tests/i18n/other/locale/fr/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/__init__.py (renamed from tests/regressiontests/admin_validation/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/locale/en/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/patterns/locale/en/LC_MESSAGES/django.mo) | bin | 651 -> 651 bytes | |||
| -rw-r--r-- | tests/i18n/patterns/locale/en/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/patterns/locale/en/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/locale/nl/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/patterns/locale/nl/LC_MESSAGES/django.mo) | bin | 696 -> 696 bytes | |||
| -rw-r--r-- | tests/i18n/patterns/locale/nl/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/patterns/locale/nl/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.mo) | bin | 696 -> 696 bytes | |||
| -rw-r--r-- | tests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/templates/404.html (renamed from tests/regressiontests/i18n/patterns/templates/404.html) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/templates/dummy.html (renamed from tests/regressiontests/i18n/patterns/templates/dummy.html) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/tests.py (renamed from tests/regressiontests/i18n/patterns/tests.py) | 42 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/__init__.py (renamed from tests/regressiontests/admin_views/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/default.py (renamed from tests/regressiontests/i18n/patterns/urls/default.py) | 3 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/disabled.py (renamed from tests/regressiontests/i18n/patterns/urls/disabled.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/namespace.py (renamed from tests/regressiontests/i18n/patterns/urls/namespace.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/path_unused.py (renamed from tests/regressiontests/i18n/patterns/urls/path_unused.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/wrong.py (renamed from tests/regressiontests/i18n/patterns/urls/wrong.py) | 2 | ||||
| -rw-r--r-- | tests/i18n/patterns/urls/wrong_namespace.py (renamed from tests/regressiontests/i18n/patterns/urls/wrong_namespace.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/resolution/__init__.py (renamed from tests/regressiontests/admin_widgets/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/resolution/locale/de/LC_MESSAGES/django.mo (renamed from tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo) | bin | 489 -> 489 bytes | |||
| -rw-r--r-- | tests/i18n/resolution/locale/de/LC_MESSAGES/django.po (renamed from tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po) | 0 | ||||
| -rw-r--r-- | tests/i18n/resolution/models.py (renamed from tests/regressiontests/i18n/resolution/models.py) | 0 | ||||
| -rw-r--r-- | tests/i18n/tests.py (renamed from tests/regressiontests/i18n/tests.py) | 331 | ||||
| -rw-r--r-- | tests/i18n/urls.py | 9 | ||||
| -rw-r--r-- | tests/indexes/__init__.py (renamed from tests/regressiontests/aggregation_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/indexes/models.py | 20 | ||||
| -rw-r--r-- | tests/indexes/tests.py | 27 | ||||
| -rw-r--r-- | tests/initial_sql_regress/__init__.py (renamed from tests/regressiontests/app_loading/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/initial_sql_regress/models.py (renamed from tests/regressiontests/initial_sql_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/initial_sql_regress/sql/simple.sql (renamed from tests/regressiontests/initial_sql_regress/sql/simple.sql) | 1 | ||||
| -rw-r--r-- | tests/initial_sql_regress/tests.py | 47 | ||||
| -rw-r--r-- | tests/inline_formsets/__init__.py (renamed from tests/regressiontests/app_loading/not_installed/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/inline_formsets/models.py (renamed from tests/regressiontests/inline_formsets/models.py) | 0 | ||||
| -rw-r--r-- | tests/inline_formsets/tests.py (renamed from tests/regressiontests/inline_formsets/tests.py) | 6 | ||||
| -rw-r--r-- | tests/inspectdb/__init__.py (renamed from tests/regressiontests/bash_completion/management/commands/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/inspectdb/models.py | 60 | ||||
| -rw-r--r-- | tests/inspectdb/tests.py | 164 | ||||
| -rw-r--r-- | tests/introspection/__init__.py (renamed from tests/regressiontests/backends/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/introspection/models.py (renamed from tests/regressiontests/introspection/models.py) | 2 | ||||
| -rw-r--r-- | tests/introspection/tests.py (renamed from tests/regressiontests/introspection/tests.py) | 52 | ||||
| -rw-r--r-- | tests/invalid_models/__init__.py (renamed from tests/regressiontests/delete_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/invalid_models/invalid_models/__init__.py (renamed from tests/regressiontests/bug639/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/invalid_models/invalid_models/models.py (renamed from tests/modeltests/invalid_models/invalid_models/models.py) | 0 | ||||
| -rw-r--r-- | tests/invalid_models/models.py (renamed from tests/regressiontests/localflavor/models.py) | 0 | ||||
| -rw-r--r-- | tests/invalid_models/tests.py (renamed from tests/modeltests/invalid_models/tests.py) | 2 | ||||
| -rw-r--r-- | tests/known_related_objects/__init__.py (renamed from tests/regressiontests/bug8245/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/known_related_objects/fixtures/tournament.json (renamed from tests/modeltests/known_related_objects/fixtures/tournament.json) | 41 | ||||
| -rw-r--r-- | tests/known_related_objects/models.py (renamed from tests/modeltests/known_related_objects/models.py) | 4 | ||||
| -rw-r--r-- | tests/known_related_objects/tests.py (renamed from tests/modeltests/known_related_objects/tests.py) | 52 | ||||
| -rw-r--r-- | tests/logging_tests/__init__.py (renamed from tests/regressiontests/builtin_server/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/logging_tests/logconfig.py (renamed from tests/regressiontests/logging_tests/logconfig.py) | 0 | ||||
| -rw-r--r-- | tests/logging_tests/models.py (renamed from tests/regressiontests/logging_tests/models.py) | 0 | ||||
| -rw-r--r-- | tests/logging_tests/tests.py (renamed from tests/regressiontests/logging_tests/tests.py) | 117 | ||||
| -rw-r--r-- | tests/lookup/__init__.py (renamed from tests/regressiontests/bulk_create/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/lookup/models.py (renamed from tests/modeltests/lookup/models.py) | 21 | ||||
| -rw-r--r-- | tests/lookup/tests.py | 687 | ||||
| -rw-r--r-- | tests/m2m_and_m2o/__init__.py (renamed from tests/regressiontests/cache/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_and_m2o/models.py (renamed from tests/modeltests/m2m_and_m2o/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_and_m2o/tests.py (renamed from tests/modeltests/m2m_and_m2o/tests.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_intermediary/__init__.py (renamed from tests/regressiontests/comment_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_intermediary/models.py (renamed from tests/modeltests/m2m_intermediary/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_intermediary/tests.py (renamed from tests/modeltests/m2m_intermediary/tests.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_multiple/__init__.py (renamed from tests/regressiontests/context_processors/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_multiple/models.py (renamed from tests/modeltests/m2m_multiple/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_multiple/tests.py (renamed from tests/modeltests/m2m_multiple/tests.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_recursive/__init__.py (renamed from tests/regressiontests/createsuperuser/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_recursive/models.py (renamed from tests/modeltests/m2m_recursive/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_recursive/tests.py (renamed from tests/modeltests/m2m_recursive/tests.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_regress/__init__.py (renamed from tests/regressiontests/csrf_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_regress/models.py (renamed from tests/regressiontests/m2m_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_regress/tests.py (renamed from tests/regressiontests/m2m_regress/tests.py) | 15 | ||||
| -rw-r--r-- | tests/m2m_signals/__init__.py (renamed from tests/regressiontests/file_storage/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_signals/models.py (renamed from tests/modeltests/m2m_signals/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_signals/tests.py (renamed from tests/modeltests/m2m_signals/tests.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_through/__init__.py (renamed from tests/modeltests/m2m_through/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_through/models.py (renamed from tests/modeltests/m2m_through/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_through/tests.py (renamed from tests/modeltests/m2m_through/tests.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_through_regress/__init__.py (renamed from tests/modeltests/unmanaged_models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_through_regress/fixtures/m2m_through.json (renamed from tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json) | 0 | ||||
| -rw-r--r-- | tests/m2m_through_regress/models.py (renamed from tests/regressiontests/m2m_through_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2m_through_regress/tests.py (renamed from tests/regressiontests/m2m_through_regress/tests.py) | 6 | ||||
| -rw-r--r-- | tests/m2o_recursive/__init__.py (renamed from tests/regressiontests/custom_columns_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/m2o_recursive/models.py (renamed from tests/modeltests/m2o_recursive/models.py) | 0 | ||||
| -rw-r--r-- | tests/m2o_recursive/tests.py (renamed from tests/modeltests/m2o_recursive/tests.py) | 0 | ||||
| -rw-r--r-- | tests/mail/__init__.py (renamed from tests/regressiontests/m2m_through_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/mail/custombackend.py (renamed from tests/regressiontests/mail/custombackend.py) | 0 | ||||
| -rw-r--r-- | tests/mail/models.py (renamed from tests/regressiontests/mail/models.py) | 0 | ||||
| -rw-r--r-- | tests/mail/tests.py (renamed from tests/regressiontests/mail/tests.py) | 51 | ||||
| -rw-r--r-- | tests/managers_regress/__init__.py (renamed from tests/regressiontests/custom_managers_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/managers_regress/models.py (renamed from tests/regressiontests/managers_regress/models.py) | 12 | ||||
| -rw-r--r-- | tests/managers_regress/tests.py (renamed from tests/regressiontests/managers_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_many/__init__.py (renamed from tests/regressiontests/datatypes/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_many/models.py (renamed from tests/modeltests/many_to_many/models.py) | 5 | ||||
| -rw-r--r-- | tests/many_to_many/tests.py (renamed from tests/modeltests/many_to_many/tests.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one/__init__.py (renamed from tests/regressiontests/dates/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one/models.py (renamed from tests/modeltests/many_to_one/models.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one/tests.py (renamed from tests/modeltests/many_to_one/tests.py) | 56 | ||||
| -rw-r--r-- | tests/many_to_one_null/__init__.py (renamed from tests/regressiontests/db_typecasts/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one_null/models.py (renamed from tests/modeltests/many_to_one_null/models.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one_null/tests.py (renamed from tests/modeltests/many_to_one_null/tests.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one_regress/__init__.py (renamed from tests/regressiontests/decorators/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/many_to_one_regress/models.py (renamed from tests/regressiontests/many_to_one_regress/models.py) | 6 | ||||
| -rw-r--r-- | tests/many_to_one_regress/tests.py (renamed from tests/regressiontests/many_to_one_regress/tests.py) | 25 | ||||
| -rw-r--r-- | tests/max_lengths/__init__.py (renamed from tests/regressiontests/inspectdb/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/max_lengths/models.py (renamed from tests/regressiontests/max_lengths/models.py) | 0 | ||||
| -rw-r--r-- | tests/max_lengths/tests.py (renamed from tests/regressiontests/max_lengths/tests.py) | 0 | ||||
| -rw-r--r-- | tests/middleware/__init__.py (renamed from tests/regressiontests/defaultfilters/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/middleware/cond_get_urls.py (renamed from tests/regressiontests/middleware/cond_get_urls.py) | 0 | ||||
| -rw-r--r-- | tests/middleware/extra_urls.py (renamed from tests/regressiontests/middleware/extra_urls.py) | 0 | ||||
| -rw-r--r-- | tests/middleware/models.py | 13 | ||||
| -rw-r--r-- | tests/middleware/tests.py (renamed from tests/regressiontests/middleware/tests.py) | 506 | ||||
| -rw-r--r-- | tests/middleware/urls.py (renamed from tests/regressiontests/middleware/urls.py) | 0 | ||||
| -rw-r--r-- | tests/middleware_exceptions/__init__.py (renamed from tests/regressiontests/defer_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/middleware_exceptions/models.py (renamed from tests/regressiontests/middleware_exceptions/models.py) | 0 | ||||
| -rw-r--r-- | tests/middleware_exceptions/tests.py (renamed from tests/regressiontests/middleware_exceptions/tests.py) | 12 | ||||
| -rw-r--r-- | tests/middleware_exceptions/urls.py (renamed from tests/regressiontests/middleware_exceptions/urls.py) | 0 | ||||
| -rw-r--r-- | tests/middleware_exceptions/views.py (renamed from tests/regressiontests/middleware_exceptions/views.py) | 0 | ||||
| -rw-r--r-- | tests/model_fields/4x8.png (renamed from tests/regressiontests/model_fields/4x8.png) | bin | 87 -> 87 bytes | |||
| -rw-r--r-- | tests/model_fields/8x4.png (renamed from tests/regressiontests/model_fields/8x4.png) | bin | 87 -> 87 bytes | |||
| -rw-r--r-- | tests/model_fields/__init__.py (renamed from tests/regressiontests/expressions_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_fields/models.py (renamed from tests/regressiontests/model_fields/models.py) | 4 | ||||
| -rw-r--r-- | tests/model_fields/test_imagefield.py (renamed from tests/regressiontests/model_fields/imagefield.py) | 0 | ||||
| -rw-r--r-- | tests/model_fields/tests.py (renamed from tests/regressiontests/model_fields/tests.py) | 52 | ||||
| -rw-r--r-- | tests/model_forms/__init__.py (renamed from tests/regressiontests/extra_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_forms/models.py (renamed from tests/modeltests/model_forms/models.py) | 42 | ||||
| -rw-r--r-- | tests/model_forms/test.png (renamed from tests/regressiontests/file_storage/test.png) | bin | 482 -> 482 bytes | |||
| -rw-r--r-- | tests/model_forms/test2.png (renamed from tests/modeltests/model_forms/test2.png) | bin | 2072 -> 2072 bytes | |||
| -rw-r--r-- | tests/model_forms/tests.py (renamed from tests/modeltests/model_forms/tests.py) | 160 | ||||
| -rw-r--r-- | tests/model_forms_regress/__init__.py (renamed from tests/regressiontests/file_uploads/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_forms_regress/models.py (renamed from tests/regressiontests/model_forms_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_forms_regress/tests.py (renamed from tests/regressiontests/model_forms_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/model_formsets/__init__.py (renamed from tests/regressiontests/fixtures_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_formsets/models.py (renamed from tests/modeltests/model_formsets/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_formsets/tests.py (renamed from tests/modeltests/model_formsets/tests.py) | 122 | ||||
| -rw-r--r-- | tests/model_formsets_regress/__init__.py (renamed from tests/regressiontests/forms/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_formsets_regress/models.py (renamed from tests/regressiontests/model_formsets_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_formsets_regress/tests.py (renamed from tests/regressiontests/model_formsets_regress/tests.py) | 17 | ||||
| -rw-r--r-- | tests/model_inheritance/__init__.py (renamed from tests/regressiontests/generic_inline_admin/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance/models.py (renamed from tests/modeltests/model_inheritance/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance/tests.py (renamed from tests/modeltests/model_inheritance/tests.py) | 28 | ||||
| -rw-r--r-- | tests/model_inheritance_regress/__init__.py (renamed from tests/regressiontests/generic_relations_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance_regress/models.py (renamed from tests/regressiontests/model_inheritance_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance_regress/tests.py (renamed from tests/regressiontests/model_inheritance_regress/tests.py) | 25 | ||||
| -rw-r--r-- | tests/model_inheritance_same_model_name/__init__.py (renamed from tests/regressiontests/generic_views/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance_same_model_name/models.py (renamed from tests/modeltests/model_inheritance_same_model_name/models.py) | 2 | ||||
| -rw-r--r-- | tests/model_inheritance_same_model_name/tests.py (renamed from tests/modeltests/model_inheritance_same_model_name/tests.py) | 6 | ||||
| -rw-r--r-- | tests/model_inheritance_select_related/__init__.py (renamed from tests/regressiontests/get_or_create_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance_select_related/models.py (renamed from tests/regressiontests/model_inheritance_select_related/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_inheritance_select_related/tests.py (renamed from tests/regressiontests/model_inheritance_select_related/tests.py) | 0 | ||||
| -rw-r--r-- | tests/model_package/__init__.py (renamed from tests/regressiontests/max_lengths/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_package/models/__init__.py (renamed from tests/modeltests/model_package/models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_package/models/article.py (renamed from tests/modeltests/model_package/models/article.py) | 0 | ||||
| -rw-r--r-- | tests/model_package/models/publication.py (renamed from tests/modeltests/model_package/models/publication.py) | 0 | ||||
| -rw-r--r-- | tests/model_package/tests.py (renamed from tests/modeltests/model_package/tests.py) | 0 | ||||
| -rw-r--r-- | tests/model_permalink/__init__.py (renamed from tests/regressiontests/handlers/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_permalink/models.py (renamed from tests/regressiontests/model_permalink/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_permalink/tests.py (renamed from tests/regressiontests/model_permalink/tests.py) | 2 | ||||
| -rw-r--r-- | tests/model_permalink/urls.py (renamed from tests/regressiontests/model_permalink/urls.py) | 0 | ||||
| -rw-r--r-- | tests/model_regress/__init__.py (renamed from tests/regressiontests/http_utils/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/model_regress/models.py (renamed from tests/regressiontests/model_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/model_regress/tests.py (renamed from tests/regressiontests/model_regress/tests.py) | 27 | ||||
| -rw-r--r-- | tests/modeladmin/__init__.py (renamed from tests/regressiontests/httpwrappers/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/modeladmin/models.py (renamed from tests/regressiontests/modeladmin/models.py) | 0 | ||||
| -rw-r--r-- | tests/modeladmin/tests.py (renamed from tests/regressiontests/modeladmin/tests.py) | 4 | ||||
| -rw-r--r-- | tests/modeltests/empty/no_models/tests.py | 6 | ||||
| -rw-r--r-- | tests/modeltests/get_latest/models.py | 34 | ||||
| -rw-r--r-- | tests/modeltests/get_latest/tests.py | 58 | ||||
| -rw-r--r-- | tests/modeltests/lookup/tests.py | 688 | ||||
| -rw-r--r-- | tests/modeltests/test_client/tests.py | 21 | ||||
| -rw-r--r-- | tests/modeltests/transactions/tests.py | 310 | ||||
| -rw-r--r-- | tests/multiple_database/__init__.py (renamed from tests/regressiontests/i18n/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/multiple_database/fixtures/multidb-common.json (renamed from tests/regressiontests/multiple_database/fixtures/multidb-common.json) | 0 | ||||
| -rw-r--r-- | tests/multiple_database/fixtures/multidb.default.json (renamed from tests/regressiontests/multiple_database/fixtures/multidb.default.json) | 0 | ||||
| -rw-r--r-- | tests/multiple_database/fixtures/multidb.other.json (renamed from tests/regressiontests/multiple_database/fixtures/multidb.other.json) | 0 | ||||
| -rw-r--r-- | tests/multiple_database/fixtures/pets.json (renamed from tests/regressiontests/multiple_database/fixtures/pets.json) | 0 | ||||
| -rw-r--r-- | tests/multiple_database/models.py (renamed from tests/regressiontests/multiple_database/models.py) | 0 | ||||
| -rw-r--r-- | tests/multiple_database/tests.py (renamed from tests/regressiontests/multiple_database/tests.py) | 14 | ||||
| -rw-r--r-- | tests/mutually_referential/__init__.py (renamed from tests/regressiontests/i18n/contenttypes/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/mutually_referential/models.py (renamed from tests/modeltests/mutually_referential/models.py) | 0 | ||||
| -rw-r--r-- | tests/mutually_referential/tests.py (renamed from tests/modeltests/mutually_referential/tests.py) | 0 | ||||
| -rw-r--r-- | tests/nested_foreign_keys/__init__.py (renamed from tests/regressiontests/i18n/other/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/nested_foreign_keys/models.py (renamed from tests/regressiontests/nested_foreign_keys/models.py) | 0 | ||||
| -rw-r--r-- | tests/nested_foreign_keys/tests.py (renamed from tests/regressiontests/nested_foreign_keys/tests.py) | 6 | ||||
| -rw-r--r-- | tests/null_fk/__init__.py (renamed from tests/regressiontests/i18n/other/locale/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/null_fk/models.py (renamed from tests/regressiontests/null_fk/models.py) | 0 | ||||
| -rw-r--r-- | tests/null_fk/tests.py (renamed from tests/regressiontests/null_fk/tests.py) | 0 | ||||
| -rw-r--r-- | tests/null_fk_ordering/__init__.py (renamed from tests/regressiontests/i18n/other/locale/de/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/null_fk_ordering/models.py (renamed from tests/regressiontests/null_fk_ordering/models.py) | 0 | ||||
| -rw-r--r-- | tests/null_fk_ordering/tests.py (renamed from tests/regressiontests/null_fk_ordering/tests.py) | 0 | ||||
| -rw-r--r-- | tests/null_queries/__init__.py (renamed from tests/regressiontests/i18n/patterns/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/null_queries/models.py (renamed from tests/regressiontests/null_queries/models.py) | 3 | ||||
| -rw-r--r-- | tests/null_queries/tests.py (renamed from tests/regressiontests/null_queries/tests.py) | 6 | ||||
| -rw-r--r-- | tests/one_to_one/__init__.py (renamed from tests/regressiontests/i18n/patterns/urls/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/one_to_one/models.py (renamed from tests/modeltests/one_to_one/models.py) | 0 | ||||
| -rw-r--r-- | tests/one_to_one/tests.py (renamed from tests/modeltests/one_to_one/tests.py) | 0 | ||||
| -rw-r--r-- | tests/one_to_one_regress/__init__.py (renamed from tests/regressiontests/i18n/resolution/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/one_to_one_regress/models.py (renamed from tests/regressiontests/one_to_one_regress/models.py) | 4 | ||||
| -rw-r--r-- | tests/one_to_one_regress/tests.py (renamed from tests/regressiontests/one_to_one_regress/tests.py) | 0 | ||||
| -rw-r--r-- | tests/or_lookups/__init__.py (renamed from tests/regressiontests/indexes/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/or_lookups/models.py (renamed from tests/modeltests/or_lookups/models.py) | 0 | ||||
| -rw-r--r-- | tests/or_lookups/tests.py (renamed from tests/modeltests/or_lookups/tests.py) | 0 | ||||
| -rw-r--r-- | tests/order_with_respect_to/__init__.py (renamed from tests/regressiontests/initial_sql_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/order_with_respect_to/models.py (renamed from tests/modeltests/order_with_respect_to/models.py) | 0 | ||||
| -rw-r--r-- | tests/order_with_respect_to/tests.py (renamed from tests/modeltests/order_with_respect_to/tests.py) | 0 | ||||
| -rw-r--r-- | tests/ordering/__init__.py (renamed from tests/regressiontests/inline_formsets/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/ordering/models.py (renamed from tests/modeltests/ordering/models.py) | 0 | ||||
| -rw-r--r-- | tests/ordering/tests.py (renamed from tests/modeltests/ordering/tests.py) | 0 | ||||
| -rw-r--r-- | tests/pagination/__init__.py (renamed from tests/regressiontests/introspection/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/pagination/custom.py (renamed from tests/regressiontests/pagination/custom.py) | 0 | ||||
| -rw-r--r-- | tests/pagination/models.py (renamed from tests/regressiontests/pagination/models.py) | 0 | ||||
| -rw-r--r-- | tests/pagination/tests.py (renamed from tests/regressiontests/pagination/tests.py) | 0 | ||||
| -rw-r--r-- | tests/prefetch_related/__init__.py (renamed from tests/regressiontests/localflavor/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/prefetch_related/models.py (renamed from tests/modeltests/prefetch_related/models.py) | 4 | ||||
| -rw-r--r-- | tests/prefetch_related/tests.py (renamed from tests/modeltests/prefetch_related/tests.py) | 0 | ||||
| -rw-r--r-- | tests/properties/__init__.py (renamed from tests/regressiontests/localflavor/generic/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/properties/models.py (renamed from tests/modeltests/properties/models.py) | 0 | ||||
| -rw-r--r-- | tests/properties/tests.py (renamed from tests/modeltests/properties/tests.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/__init__.py (renamed from tests/regressiontests/logging_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/app1/__init__.py (renamed from tests/regressiontests/m2m_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/app1/models.py (renamed from tests/modeltests/proxy_model_inheritance/app1/models.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/app2/__init__.py (renamed from tests/regressiontests/managers_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/app2/models.py (renamed from tests/modeltests/proxy_model_inheritance/app2/models.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/models.py (renamed from tests/modeltests/proxy_model_inheritance/models.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_model_inheritance/tests.py (renamed from tests/modeltests/proxy_model_inheritance/tests.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_models/__init__.py (renamed from tests/regressiontests/many_to_one_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/proxy_models/fixtures/myhorses.json | 24 | ||||
| -rw-r--r-- | tests/proxy_models/fixtures/mypeople.json (renamed from tests/modeltests/proxy_models/fixtures/mypeople.json) | 0 | ||||
| -rw-r--r-- | tests/proxy_models/models.py (renamed from tests/modeltests/proxy_models/models.py) | 8 | ||||
| -rw-r--r-- | tests/proxy_models/tests.py (renamed from tests/modeltests/proxy_models/tests.py) | 22 | ||||
| -rw-r--r-- | tests/queries/__init__.py (renamed from tests/regressiontests/middleware/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/queries/models.py (renamed from tests/regressiontests/queries/models.py) | 84 | ||||
| -rw-r--r-- | tests/queries/tests.py (renamed from tests/regressiontests/queries/tests.py) | 533 | ||||
| -rw-r--r-- | tests/queryset_pickle/__init__.py (renamed from tests/regressiontests/middleware_exceptions/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/queryset_pickle/models.py (renamed from tests/regressiontests/queryset_pickle/models.py) | 6 | ||||
| -rw-r--r-- | tests/queryset_pickle/tests.py (renamed from tests/regressiontests/queryset_pickle/tests.py) | 3 | ||||
| -rw-r--r-- | tests/raw_query/__init__.py (renamed from tests/regressiontests/model_fields/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/raw_query/fixtures/raw_query_books.json (renamed from tests/modeltests/raw_query/fixtures/raw_query_books.json) | 0 | ||||
| -rw-r--r-- | tests/raw_query/models.py (renamed from tests/modeltests/raw_query/models.py) | 0 | ||||
| -rw-r--r-- | tests/raw_query/tests.py (renamed from tests/modeltests/raw_query/tests.py) | 0 | ||||
| -rw-r--r-- | tests/regressiontests/builtin_server/tests.py | 55 | ||||
| -rw-r--r-- | tests/regressiontests/comment_tests/custom_comments/__init__.py | 32 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests/__init__.py | 21 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests/validators.py | 16 | ||||
| -rw-r--r-- | tests/regressiontests/generic_views/tests.py | 11 | ||||
| -rw-r--r-- | tests/regressiontests/handlers/tests.py | 28 | ||||
| -rw-r--r-- | tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.mo | bin | 775 -> 0 bytes | |||
| -rw-r--r-- | tests/regressiontests/i18n/commands/tests.py | 48 | ||||
| -rw-r--r-- | tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo | bin | 1449 -> 0 bytes | |||
| -rw-r--r-- | tests/regressiontests/indexes/models.py | 11 | ||||
| -rw-r--r-- | tests/regressiontests/indexes/tests.py | 12 | ||||
| -rw-r--r-- | tests/regressiontests/initial_sql_regress/tests.py | 28 | ||||
| -rw-r--r-- | tests/regressiontests/inspectdb/models.py | 30 | ||||
| -rw-r--r-- | tests/regressiontests/inspectdb/tests.py | 76 | ||||
| -rw-r--r-- | tests/regressiontests/localflavor/generic/tests.py | 90 | ||||
| -rw-r--r-- | tests/regressiontests/localflavor/tests.py | 3 | ||||
| -rw-r--r-- | tests/regressiontests/signals_regress/models.py | 18 | ||||
| -rw-r--r-- | tests/regressiontests/signing/models.py | 1 | ||||
| -rw-r--r-- | tests/regressiontests/urlpatterns_reverse/erroneous_urls.py | 16 | ||||
| -rw-r--r-- | tests/regressiontests/urlpatterns_reverse/middleware.py | 12 | ||||
| -rw-r--r-- | tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py | 8 | ||||
| -rw-r--r-- | tests/regressiontests/utils/models.py | 1 | ||||
| -rw-r--r-- | tests/regressiontests/utils/tests.py | 32 | ||||
| -rw-r--r-- | tests/regressiontests/views/tests/__init__.py | 10 | ||||
| -rw-r--r-- | tests/requests/__init__.py (renamed from tests/regressiontests/requests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/requests/models.py (renamed from tests/regressiontests/requests/models.py) | 0 | ||||
| -rw-r--r-- | tests/requests/tests.py (renamed from tests/regressiontests/requests/tests.py) | 205 | ||||
| -rw-r--r-- | tests/reserved_names/__init__.py (renamed from tests/regressiontests/model_forms_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/reserved_names/models.py (renamed from tests/modeltests/reserved_names/models.py) | 0 | ||||
| -rw-r--r-- | tests/reserved_names/tests.py (renamed from tests/modeltests/reserved_names/tests.py) | 4 | ||||
| -rw-r--r-- | tests/resolve_url/__init__.py (renamed from tests/regressiontests/model_formsets_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/resolve_url/models.py (renamed from tests/regressiontests/resolve_url/models.py) | 0 | ||||
| -rw-r--r-- | tests/resolve_url/tests.py (renamed from tests/regressiontests/resolve_url/tests.py) | 0 | ||||
| -rw-r--r-- | tests/reverse_lookup/__init__.py (renamed from tests/regressiontests/model_inheritance_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/reverse_lookup/models.py (renamed from tests/modeltests/reverse_lookup/models.py) | 0 | ||||
| -rw-r--r-- | tests/reverse_lookup/tests.py (renamed from tests/modeltests/reverse_lookup/tests.py) | 0 | ||||
| -rw-r--r-- | tests/reverse_single_related/__init__.py (renamed from tests/regressiontests/model_inheritance_select_related/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/reverse_single_related/models.py (renamed from tests/regressiontests/reverse_single_related/models.py) | 4 | ||||
| -rw-r--r-- | tests/reverse_single_related/tests.py (renamed from tests/regressiontests/reverse_single_related/tests.py) | 0 | ||||
| -rwxr-xr-x | tests/runtests.py | 72 | ||||
| -rw-r--r-- | tests/save_delete_hooks/__init__.py (renamed from tests/regressiontests/model_permalink/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/save_delete_hooks/models.py (renamed from tests/modeltests/save_delete_hooks/models.py) | 0 | ||||
| -rw-r--r-- | tests/save_delete_hooks/tests.py (renamed from tests/modeltests/save_delete_hooks/tests.py) | 0 | ||||
| -rw-r--r-- | tests/schema/__init__.py (renamed from tests/regressiontests/model_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/schema/models.py (renamed from tests/modeltests/schema/models.py) | 0 | ||||
| -rw-r--r-- | tests/schema/tests.py (renamed from tests/modeltests/schema/tests.py) | 11 | ||||
| -rw-r--r-- | tests/select_for_update/__init__.py (renamed from tests/regressiontests/admin_custom_urls/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/select_for_update/models.py (renamed from tests/modeltests/select_for_update/models.py) | 0 | ||||
| -rw-r--r-- | tests/select_for_update/tests.py (renamed from tests/modeltests/select_for_update/tests.py) | 33 | ||||
| -rw-r--r-- | tests/select_related/__init__.py (renamed from tests/regressiontests/modeladmin/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/select_related/models.py (renamed from tests/modeltests/select_related/models.py) | 0 | ||||
| -rw-r--r-- | tests/select_related/tests.py (renamed from tests/modeltests/select_related/tests.py) | 32 | ||||
| -rw-r--r-- | tests/select_related_onetoone/__init__.py (renamed from tests/regressiontests/multiple_database/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/select_related_onetoone/models.py (renamed from tests/regressiontests/select_related_onetoone/models.py) | 0 | ||||
| -rw-r--r-- | tests/select_related_onetoone/tests.py (renamed from tests/regressiontests/select_related_onetoone/tests.py) | 10 | ||||
| -rw-r--r-- | tests/select_related_regress/__init__.py (renamed from tests/regressiontests/nested_foreign_keys/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/select_related_regress/models.py (renamed from tests/regressiontests/select_related_regress/models.py) | 14 | ||||
| -rw-r--r-- | tests/select_related_regress/tests.py (renamed from tests/regressiontests/select_related_regress/tests.py) | 36 | ||||
| -rw-r--r-- | tests/serializers/__init__.py (renamed from tests/regressiontests/null_fk/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/serializers/models.py (renamed from tests/modeltests/serializers/models.py) | 0 | ||||
| -rw-r--r-- | tests/serializers/tests.py (renamed from tests/modeltests/serializers/tests.py) | 19 | ||||
| -rw-r--r-- | tests/serializers_regress/__init__.py (renamed from tests/regressiontests/null_fk_ordering/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/serializers_regress/models.py (renamed from tests/regressiontests/serializers_regress/models.py) | 3 | ||||
| -rw-r--r-- | tests/serializers_regress/tests.py (renamed from tests/regressiontests/serializers_regress/tests.py) | 40 | ||||
| -rw-r--r-- | tests/servers/__init__.py (renamed from tests/regressiontests/null_queries/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/servers/fixtures/testdata.json (renamed from tests/regressiontests/servers/fixtures/testdata.json) | 0 | ||||
| -rw-r--r-- | tests/servers/media/example_media_file.txt (renamed from tests/regressiontests/servers/media/example_media_file.txt) | 0 | ||||
| -rw-r--r-- | tests/servers/models.py (renamed from tests/regressiontests/servers/models.py) | 0 | ||||
| -rw-r--r-- | tests/servers/static/example_static_file.txt (renamed from tests/regressiontests/servers/static/example_static_file.txt) | 0 | ||||
| -rw-r--r-- | tests/servers/tests.py (renamed from tests/regressiontests/servers/tests.py) | 4 | ||||
| -rw-r--r-- | tests/servers/urls.py (renamed from tests/regressiontests/servers/urls.py) | 0 | ||||
| -rw-r--r-- | tests/servers/views.py (renamed from tests/regressiontests/servers/views.py) | 0 | ||||
| -rw-r--r-- | tests/settings_tests/__init__.py (renamed from tests/regressiontests/one_to_one_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/settings_tests/models.py (renamed from tests/regressiontests/settings_tests/models.py) | 0 | ||||
| -rw-r--r-- | tests/settings_tests/tests.py (renamed from tests/regressiontests/settings_tests/tests.py) | 64 | ||||
| -rw-r--r-- | tests/shared_models/__init__.py (renamed from tests/regressiontests/pagination/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/shared_models/models.py | 37 | ||||
| -rw-r--r-- | tests/signals/__init__.py (renamed from tests/regressiontests/queries/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/signals/models.py (renamed from tests/modeltests/signals/models.py) | 0 | ||||
| -rw-r--r-- | tests/signals/tests.py (renamed from tests/modeltests/signals/tests.py) | 0 | ||||
| -rw-r--r-- | tests/signals_regress/__init__.py (renamed from tests/regressiontests/queryset_pickle/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/signals_regress/models.py (renamed from tests/regressiontests/staticfiles_tests/apps/no_label/models.py) | 0 | ||||
| -rw-r--r-- | tests/signals_regress/tests.py (renamed from tests/regressiontests/signals_regress/tests.py) | 6 | ||||
| -rw-r--r-- | tests/signed_cookies_tests/__init__.py (renamed from tests/regressiontests/resolve_url/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/signed_cookies_tests/models.py (renamed from tests/regressiontests/middleware/models.py) | 0 | ||||
| -rw-r--r-- | tests/signed_cookies_tests/tests.py (renamed from tests/regressiontests/signed_cookies_tests/tests.py) | 0 | ||||
| -rw-r--r-- | tests/signing/__init__.py (renamed from tests/regressiontests/reverse_single_related/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/signing/models.py (renamed from tests/regressiontests/signed_cookies_tests/models.py) | 0 | ||||
| -rw-r--r-- | tests/signing/tests.py (renamed from tests/regressiontests/signing/tests.py) | 0 | ||||
| -rw-r--r-- | tests/sites_framework/__init__.py (renamed from tests/regressiontests/select_related_onetoone/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/sites_framework/models.py (renamed from tests/regressiontests/sites_framework/models.py) | 0 | ||||
| -rw-r--r-- | tests/sites_framework/tests.py (renamed from tests/regressiontests/sites_framework/tests.py) | 0 | ||||
| -rw-r--r-- | tests/special_headers/__init__.py (renamed from tests/regressiontests/select_related_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/special_headers/fixtures/data.xml (renamed from tests/regressiontests/special_headers/fixtures/data.xml) | 0 | ||||
| -rw-r--r-- | tests/special_headers/models.py (renamed from tests/regressiontests/special_headers/models.py) | 0 | ||||
| -rw-r--r-- | tests/special_headers/templates/special_headers/article_detail.html (renamed from tests/regressiontests/special_headers/templates/special_headers/article_detail.html) | 0 | ||||
| -rw-r--r-- | tests/special_headers/tests.py (renamed from tests/regressiontests/special_headers/tests.py) | 6 | ||||
| -rw-r--r-- | tests/special_headers/urls.py (renamed from tests/regressiontests/special_headers/urls.py) | 0 | ||||
| -rw-r--r-- | tests/special_headers/views.py (renamed from tests/regressiontests/special_headers/views.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/__init__.py (renamed from tests/regressiontests/serializers_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/__init__.py (renamed from tests/regressiontests/servers/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/no_label/__init__.py (renamed from tests/regressiontests/settings_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/no_label/models.py (renamed from tests/regressiontests/staticfiles_tests/apps/test/models.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/no_label/static/file2.txt (renamed from tests/regressiontests/staticfiles_tests/apps/no_label/static/file2.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/__init__.py (renamed from tests/regressiontests/signals_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/models.py (renamed from tests/regressiontests/staticfiles_tests/models.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/otherdir/odfile.txt (renamed from tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/static/test/CVS (renamed from tests/regressiontests/staticfiles_tests/apps/test/static/test/CVS) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/static/test/file.txt (renamed from tests/regressiontests/staticfiles_tests/apps/test/static/test/file.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/static/test/file1.txt (renamed from tests/regressiontests/staticfiles_tests/apps/test/static/test/file1.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/static/test/nonascii.css (renamed from tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/static/test/test.ignoreme (renamed from tests/regressiontests/staticfiles_tests/apps/test/static/test/test.ignoreme) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/apps/test/static/test/window.png (renamed from tests/regressiontests/staticfiles_tests/apps/test/static/test/window.png) | bin | 207 -> 207 bytes | |||
| -rw-r--r-- | tests/staticfiles_tests/models.py (renamed from tests/regressiontests/templates/models.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/absolute.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/absolute.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.eot) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/css/fonts/font.svg (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.svg) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/css/fragments.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/css/fragments.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/css/ignored.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/css/img/window.png (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/css/img/window.png) | bin | 207 -> 207 bytes | |||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/css/window.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/css/window.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/denorm.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/denorm.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/img/relative.png (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/img/relative.png) | bin | 207 -> 207 bytes | |||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/import.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/import.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/other.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/other.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/relative.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/relative.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/styles.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/styles.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/styles_insensitive.css | 1 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/cached/url.css (renamed from tests/regressiontests/staticfiles_tests/project/documents/cached/url.css) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/subdir/test.txt (renamed from tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/test.txt (renamed from tests/regressiontests/staticfiles_tests/project/documents/test.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/test/camelCase.txt (renamed from tests/regressiontests/staticfiles_tests/project/documents/test/camelCase.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/documents/test/file.txt (renamed from tests/regressiontests/staticfiles_tests/project/documents/test/file.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/prefixed/test.txt (renamed from tests/regressiontests/staticfiles_tests/project/prefixed/test.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/site_media/media/media-file.txt (renamed from tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/project/site_media/static/testfile.txt (renamed from tests/regressiontests/staticfiles_tests/project/site_media/static/testfile.txt) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/storage.py (renamed from tests/regressiontests/staticfiles_tests/storage.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/tests.py (renamed from tests/regressiontests/staticfiles_tests/tests.py) | 38 | ||||
| -rw-r--r-- | tests/staticfiles_tests/urls/__init__.py (renamed from tests/regressiontests/signed_cookies_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/urls/default.py (renamed from tests/regressiontests/staticfiles_tests/urls/default.py) | 0 | ||||
| -rw-r--r-- | tests/staticfiles_tests/urls/helper.py (renamed from tests/regressiontests/staticfiles_tests/urls/helper.py) | 0 | ||||
| -rw-r--r-- | tests/str/__init__.py (renamed from tests/regressiontests/signing/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/str/models.py (renamed from tests/modeltests/str/models.py) | 0 | ||||
| -rw-r--r-- | tests/str/tests.py (renamed from tests/modeltests/str/tests.py) | 2 | ||||
| -rw-r--r-- | tests/string_lookup/__init__.py (renamed from tests/regressiontests/sites_framework/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/string_lookup/models.py (renamed from tests/regressiontests/string_lookup/models.py) | 0 | ||||
| -rw-r--r-- | tests/string_lookup/tests.py (renamed from tests/regressiontests/string_lookup/tests.py) | 0 | ||||
| -rw-r--r-- | tests/swappable_models/__init__.py (renamed from tests/regressiontests/special_headers/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/swappable_models/models.py (renamed from tests/regressiontests/swappable_models/models.py) | 0 | ||||
| -rw-r--r-- | tests/swappable_models/tests.py (renamed from tests/regressiontests/swappable_models/tests.py) | 12 | ||||
| -rw-r--r-- | tests/syndication/__init__.py (renamed from tests/regressiontests/staticfiles_tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/syndication/feeds.py (renamed from tests/regressiontests/syndication/feeds.py) | 26 | ||||
| -rw-r--r-- | tests/syndication/fixtures/feeddata.json (renamed from tests/regressiontests/syndication/fixtures/feeddata.json) | 0 | ||||
| -rw-r--r-- | tests/syndication/models.py (renamed from tests/regressiontests/syndication/models.py) | 0 | ||||
| -rw-r--r-- | tests/syndication/templates/syndication/description.html (renamed from tests/regressiontests/syndication/templates/syndication/description.html) | 0 | ||||
| -rw-r--r-- | tests/syndication/templates/syndication/description_context.html | 1 | ||||
| -rw-r--r-- | tests/syndication/templates/syndication/title.html (renamed from tests/regressiontests/syndication/templates/syndication/title.html) | 0 | ||||
| -rw-r--r-- | tests/syndication/templates/syndication/title_context.html | 1 | ||||
| -rw-r--r-- | tests/syndication/tests.py (renamed from tests/regressiontests/syndication/tests.py) | 54 | ||||
| -rw-r--r-- | tests/syndication/urls.py (renamed from tests/regressiontests/syndication/urls.py) | 5 | ||||
| -rw-r--r-- | tests/tablespaces/__init__.py (renamed from tests/regressiontests/staticfiles_tests/apps/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/tablespaces/models.py (renamed from tests/modeltests/tablespaces/models.py) | 0 | ||||
| -rw-r--r-- | tests/tablespaces/tests.py (renamed from tests/modeltests/tablespaces/tests.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/__init__.py (renamed from tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/alternate_urls.py (renamed from tests/regressiontests/templates/alternate_urls.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/eggs/tagsegg.egg (renamed from tests/regressiontests/templates/eggs/tagsegg.egg) | bin | 2581 -> 2581 bytes | |||
| -rw-r--r-- | tests/template_tests/filters.py (renamed from tests/regressiontests/templates/filters.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/loaders.py (renamed from tests/regressiontests/templates/loaders.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/models.py (renamed from tests/regressiontests/test_client_regress/models.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/broken_base.html (renamed from tests/regressiontests/templates/templates/broken_base.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/first/test.html (renamed from tests/regressiontests/templates/templates/first/test.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/inclusion.html (renamed from tests/regressiontests/templates/templates/inclusion.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/response.html (renamed from tests/regressiontests/templates/templates/response.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/second/test.html (renamed from tests/regressiontests/templates/templates/second/test.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/ssi include with spaces.html (renamed from tests/regressiontests/templates/templates/ssi include with spaces.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/ssi_include.html (renamed from tests/regressiontests/templates/templates/ssi_include.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/test_context.html (renamed from tests/regressiontests/templates/templates/test_context.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/test_extends_error.html (renamed from tests/regressiontests/templates/templates/test_extends_error.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/test_incl_tag_current_app.html (renamed from tests/regressiontests/templates/templates/test_incl_tag_current_app.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/test_incl_tag_use_l10n.html (renamed from tests/regressiontests/templates/templates/test_incl_tag_use_l10n.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templates/test_include_error.html (renamed from tests/regressiontests/templates/templates/test_include_error.html) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/__init__.py (renamed from tests/regressiontests/staticfiles_tests/apps/test/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/bad_tag.py (renamed from tests/regressiontests/templates/templatetags/bad_tag.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/broken_tag.py (renamed from tests/regressiontests/templates/templatetags/broken_tag.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/custom.py (renamed from tests/regressiontests/templates/templatetags/custom.py) | 7 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/subpackage/__init__.py (renamed from tests/regressiontests/staticfiles_tests/urls/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/subpackage/echo.py (renamed from tests/regressiontests/templates/templatetags/subpackage/echo.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/templatetags/subpackage/echo_invalid.py (renamed from tests/regressiontests/templates/templatetags/subpackage/echo_invalid.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/test_callables.py (renamed from tests/regressiontests/templates/callables.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/test_context.py (renamed from tests/regressiontests/templates/context.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/test_custom.py (renamed from tests/regressiontests/templates/custom.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/test_nodelist.py (renamed from tests/regressiontests/templates/nodelist.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/test_parser.py (renamed from tests/regressiontests/templates/parser.py) | 12 | ||||
| -rw-r--r-- | tests/template_tests/test_response.py (renamed from tests/regressiontests/templates/response.py) | 10 | ||||
| -rw-r--r-- | tests/template_tests/test_smartif.py (renamed from tests/regressiontests/templates/smartif.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/test_unicode.py (renamed from tests/regressiontests/templates/unicode.py) | 0 | ||||
| -rw-r--r-- | tests/template_tests/tests.py (renamed from tests/regressiontests/templates/tests.py) | 182 | ||||
| -rw-r--r-- | tests/template_tests/urls.py (renamed from tests/regressiontests/templates/urls.py) | 2 | ||||
| -rw-r--r-- | tests/template_tests/views.py (renamed from tests/regressiontests/templates/views.py) | 0 | ||||
| -rw-r--r-- | tests/test_client/__init__.py (renamed from tests/regressiontests/string_lookup/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_client/fixtures/testdata.json (renamed from tests/modeltests/test_client/fixtures/testdata.json) | 0 | ||||
| -rw-r--r-- | tests/test_client/models.py (renamed from tests/regressiontests/text/models.py) | 0 | ||||
| -rw-r--r-- | tests/test_client/tests.py (renamed from tests/modeltests/test_client/models.py) | 0 | ||||
| -rw-r--r-- | tests/test_client/urls.py (renamed from tests/modeltests/test_client/urls.py) | 0 | ||||
| -rw-r--r-- | tests/test_client/views.py (renamed from tests/modeltests/test_client/views.py) | 6 | ||||
| -rw-r--r-- | tests/test_client_regress/__init__.py (renamed from tests/regressiontests/swappable_models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/bad_templates/404.html (renamed from tests/regressiontests/test_client_regress/bad_templates/404.html) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/fixtures/testdata.json (renamed from tests/regressiontests/test_client_regress/fixtures/testdata.json) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/models.py (renamed from tests/regressiontests/urlpatterns_reverse/models.py) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/session.py (renamed from tests/regressiontests/test_client_regress/session.py) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/templates/request_context.html (renamed from tests/regressiontests/test_client_regress/templates/request_context.html) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/templates/unicode.html (renamed from tests/regressiontests/test_client_regress/templates/unicode.html) | 0 | ||||
| -rw-r--r-- | tests/test_client_regress/tests.py (renamed from tests/regressiontests/test_client_regress/tests.py) | 31 | ||||
| -rw-r--r-- | tests/test_client_regress/urls.py (renamed from tests/regressiontests/test_client_regress/urls.py) | 1 | ||||
| -rw-r--r-- | tests/test_client_regress/views.py (renamed from tests/regressiontests/test_client_regress/views.py) | 5 | ||||
| -rw-r--r-- | tests/test_runner/__init__.py (renamed from tests/regressiontests/syndication/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/deprecation_app/__init__.py (renamed from tests/regressiontests/templates/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/deprecation_app/models.py | 3 | ||||
| -rw-r--r-- | tests/test_runner/deprecation_app/tests.py | 11 | ||||
| -rw-r--r-- | tests/test_runner/invalid_app/__init__.py (renamed from tests/regressiontests/test_runner/invalid_app/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/invalid_app/models/__init__.py (renamed from tests/regressiontests/templates/templatetags/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/invalid_app/tests/__init__.py (renamed from tests/regressiontests/test_runner/invalid_app/tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/models.py (renamed from tests/regressiontests/test_runner/models.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/tests.py (renamed from tests/regressiontests/test_runner/tests.py) | 69 | ||||
| -rw-r--r-- | tests/test_runner/valid_app/__init__.py (renamed from tests/regressiontests/test_runner/valid_app/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/valid_app/models/__init__.py (renamed from tests/regressiontests/templates/templatetags/subpackage/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_runner/valid_app/tests/__init__.py (renamed from tests/regressiontests/test_client_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_sqlite.py | 5 | ||||
| -rw-r--r-- | tests/test_suite_override/__init__.py (renamed from tests/regressiontests/test_runner/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_suite_override/models.py (renamed from tests/regressiontests/version/models.py) | 0 | ||||
| -rw-r--r-- | tests/test_suite_override/tests.py | 34 | ||||
| -rw-r--r-- | tests/test_utils/__init__.py (renamed from tests/regressiontests/test_runner/invalid_app/models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/test_utils/doctest_output.py | 77 | ||||
| -rw-r--r-- | tests/test_utils/fixtures/should_not_be_loaded.json (renamed from tests/regressiontests/test_utils/fixtures/should_not_be_loaded.json) | 0 | ||||
| -rw-r--r-- | tests/test_utils/models.py (renamed from tests/regressiontests/test_utils/models.py) | 0 | ||||
| -rw-r--r-- | tests/test_utils/templates/template_used/alternative.html (renamed from tests/regressiontests/test_utils/templates/template_used/alternative.html) | 0 | ||||
| -rw-r--r-- | tests/test_utils/templates/template_used/base.html (renamed from tests/regressiontests/test_utils/templates/template_used/base.html) | 0 | ||||
| -rw-r--r-- | tests/test_utils/templates/template_used/extends.html (renamed from tests/regressiontests/test_utils/templates/template_used/extends.html) | 0 | ||||
| -rw-r--r-- | tests/test_utils/templates/template_used/include.html (renamed from tests/regressiontests/test_utils/templates/template_used/include.html) | 0 | ||||
| -rw-r--r-- | tests/test_utils/tests.py (renamed from tests/regressiontests/test_utils/tests.py) | 183 | ||||
| -rw-r--r-- | tests/test_utils/urls.py (renamed from tests/regressiontests/test_utils/urls.py) | 0 | ||||
| -rw-r--r-- | tests/test_utils/views.py (renamed from tests/regressiontests/test_utils/views.py) | 0 | ||||
| -rw-r--r-- | tests/text/__init__.py (renamed from tests/regressiontests/test_runner/valid_app/models/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/text/models.py (renamed from tests/regressiontests/wsgi/models.py) | 0 | ||||
| -rw-r--r-- | tests/text/tests.py (renamed from tests/regressiontests/text/tests.py) | 70 | ||||
| -rw-r--r-- | tests/timezones/__init__.py (renamed from tests/regressiontests/test_runner/valid_app/tests/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/timezones/admin.py (renamed from tests/modeltests/timezones/admin.py) | 0 | ||||
| -rw-r--r-- | tests/timezones/fixtures/tz_users.xml (renamed from tests/modeltests/timezones/fixtures/tz_users.xml) | 0 | ||||
| -rw-r--r-- | tests/timezones/forms.py (renamed from tests/modeltests/timezones/forms.py) | 0 | ||||
| -rw-r--r-- | tests/timezones/models.py (renamed from tests/modeltests/timezones/models.py) | 0 | ||||
| -rw-r--r-- | tests/timezones/tests.py (renamed from tests/modeltests/timezones/tests.py) | 135 | ||||
| -rw-r--r-- | tests/timezones/urls.py (renamed from tests/modeltests/timezones/urls.py) | 0 | ||||
| -rw-r--r-- | tests/transactions/__init__.py (renamed from tests/regressiontests/test_utils/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/transactions/models.py (renamed from tests/modeltests/transactions/models.py) | 2 | ||||
| -rw-r--r-- | tests/transactions/tests.py | 635 | ||||
| -rw-r--r-- | tests/transactions_regress/__init__.py (renamed from tests/regressiontests/text/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/transactions_regress/models.py (renamed from tests/regressiontests/transactions_regress/models.py) | 5 | ||||
| -rw-r--r-- | tests/transactions_regress/tests.py (renamed from tests/regressiontests/transactions_regress/tests.py) | 163 | ||||
| -rw-r--r-- | tests/unmanaged_models/__init__.py (renamed from tests/regressiontests/mail/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/unmanaged_models/models.py (renamed from tests/modeltests/unmanaged_models/models.py) | 0 | ||||
| -rw-r--r-- | tests/unmanaged_models/tests.py (renamed from tests/modeltests/unmanaged_models/tests.py) | 0 | ||||
| -rw-r--r-- | tests/update/__init__.py (renamed from tests/regressiontests/transactions_regress/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/update/models.py (renamed from tests/modeltests/update/models.py) | 0 | ||||
| -rw-r--r-- | tests/update/tests.py (renamed from tests/modeltests/update/tests.py) | 0 | ||||
| -rw-r--r-- | tests/update_only_fields/__init__.py (renamed from tests/regressiontests/urlpatterns_reverse/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/update_only_fields/models.py (renamed from tests/modeltests/update_only_fields/models.py) | 0 | ||||
| -rw-r--r-- | tests/update_only_fields/tests.py (renamed from tests/modeltests/update_only_fields/tests.py) | 6 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/__init__.py (renamed from tests/regressiontests/utils/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/erroneous_urls.py | 16 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/erroneous_views_module.py (renamed from tests/regressiontests/urlpatterns_reverse/erroneous_views_module.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/extra_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/extra_urls.py) | 4 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/included_named_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/included_named_urls.py) | 2 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/included_named_urls2.py (renamed from tests/regressiontests/urlpatterns_reverse/included_named_urls2.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/included_namespace_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py) | 6 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/included_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/included_urls.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/included_urls2.py (renamed from tests/regressiontests/urlpatterns_reverse/included_urls2.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/middleware.py | 35 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/models.py (renamed from tests/regressiontests/utils/test_module/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/named_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/named_urls.py) | 2 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/namespace_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/namespace_urls.py) | 14 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/no_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/no_urls.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/reverse_lazy_urls.py (renamed from tests/regressiontests/urlpatterns_reverse/reverse_lazy_urls.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/tests.py (renamed from tests/regressiontests/urlpatterns_reverse/tests.py) | 124 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/urlconf_inner.py (renamed from tests/regressiontests/urlpatterns_reverse/urlconf_inner.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/urlconf_outer.py (renamed from tests/regressiontests/urlpatterns_reverse/urlconf_outer.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/urls.py (renamed from tests/regressiontests/urlpatterns_reverse/urls.py) | 6 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/urls_error_handlers.py | 8 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/urls_error_handlers_callables.py (renamed from tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/urls_without_full_import.py (renamed from tests/regressiontests/urlpatterns_reverse/urls_without_full_import.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/views.py (renamed from tests/regressiontests/urlpatterns_reverse/views.py) | 0 | ||||
| -rw-r--r-- | tests/urlpatterns_reverse/views_broken.py (renamed from tests/regressiontests/urlpatterns_reverse/views_broken.py) | 0 | ||||
| -rw-r--r-- | tests/urls.py | 20 | ||||
| -rw-r--r-- | tests/user_commands/__init__.py (renamed from tests/regressiontests/version/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/user_commands/management/__init__.py (renamed from tests/regressiontests/views/templatetags/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/user_commands/management/commands/__init__.py (renamed from tests/regressiontests/wsgi/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/user_commands/management/commands/dance.py (renamed from tests/modeltests/user_commands/management/commands/dance.py) | 0 | ||||
| -rw-r--r-- | tests/user_commands/management/commands/leave_locale_alone_false.py | 10 | ||||
| -rw-r--r-- | tests/user_commands/management/commands/leave_locale_alone_true.py | 10 | ||||
| -rw-r--r-- | tests/user_commands/models.py (renamed from tests/modeltests/user_commands/models.py) | 0 | ||||
| -rw-r--r-- | tests/user_commands/tests.py (renamed from tests/modeltests/user_commands/tests.py) | 27 | ||||
| -rw-r--r-- | tests/utils_tests/__init__.py | 0 | ||||
| -rw-r--r-- | tests/utils_tests/archives/foobar.tar (renamed from tests/regressiontests/utils/archives/foobar.tar) | bin | 10240 -> 10240 bytes | |||
| -rw-r--r-- | tests/utils_tests/archives/foobar.tar.bz2 (renamed from tests/regressiontests/utils/archives/foobar.tar.bz2) | bin | 238 -> 238 bytes | |||
| -rw-r--r-- | tests/utils_tests/archives/foobar.tar.gz (renamed from tests/regressiontests/utils/archives/foobar.tar.gz) | bin | 249 -> 249 bytes | |||
| -rw-r--r-- | tests/utils_tests/archives/foobar.zip (renamed from tests/regressiontests/utils/archives/foobar.zip) | bin | 1130 -> 1130 bytes | |||
| -rw-r--r-- | tests/utils_tests/eggs/test_egg.egg (renamed from tests/regressiontests/utils/eggs/test_egg.egg) | bin | 4844 -> 4844 bytes | |||
| -rw-r--r-- | tests/utils_tests/files/strip_tags1.html | 1302 | ||||
| -rw-r--r-- | tests/utils_tests/files/strip_tags2.txt | 118 | ||||
| -rw-r--r-- | tests/utils_tests/models.py | 13 | ||||
| -rw-r--r-- | tests/utils_tests/test_archive.py (renamed from tests/regressiontests/utils/archive.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_baseconv.py (renamed from tests/regressiontests/utils/baseconv.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_checksums.py (renamed from tests/regressiontests/utils/checksums.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_crypto.py (renamed from tests/regressiontests/utils/crypto.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_datastructures.py (renamed from tests/regressiontests/utils/datastructures.py) | 4 | ||||
| -rw-r--r-- | tests/utils_tests/test_dateformat.py (renamed from tests/regressiontests/utils/dateformat.py) | 2 | ||||
| -rw-r--r-- | tests/utils_tests/test_dateparse.py (renamed from tests/regressiontests/utils/dateparse.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_datetime_safe.py (renamed from tests/regressiontests/utils/datetime_safe.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_decorators.py (renamed from tests/regressiontests/utils/decorators.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_encoding.py (renamed from tests/regressiontests/utils/encoding.py) | 8 | ||||
| -rw-r--r-- | tests/utils_tests/test_feedgenerator.py (renamed from tests/regressiontests/utils/feedgenerator.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_functional.py (renamed from tests/regressiontests/utils/functional.py) | 29 | ||||
| -rw-r--r-- | tests/utils_tests/test_html.py (renamed from tests/regressiontests/utils/html.py) | 20 | ||||
| -rw-r--r-- | tests/utils_tests/test_http.py (renamed from tests/regressiontests/utils/http.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_ipv6.py (renamed from tests/regressiontests/utils/ipv6.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_itercompat.py | 11 | ||||
| -rw-r--r-- | tests/utils_tests/test_jslex.py (renamed from tests/regressiontests/utils/jslex.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_module/__init__.py | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_module/bad_module.py (renamed from tests/regressiontests/utils/test_module/bad_module.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_module/good_module.py (renamed from tests/regressiontests/utils/test_module/good_module.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_module_loading.py (renamed from tests/regressiontests/utils/module_loading.py) | 48 | ||||
| -rw-r--r-- | tests/utils_tests/test_no_submodule.py (renamed from tests/regressiontests/utils/test_no_submodule.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_numberformat.py (renamed from tests/regressiontests/utils/numberformat.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_os_utils.py (renamed from tests/regressiontests/utils/os_utils.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_regex_helper.py (renamed from tests/regressiontests/utils/regex_helper.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_simplelazyobject.py (renamed from tests/regressiontests/utils/simplelazyobject.py) | 45 | ||||
| -rw-r--r-- | tests/utils_tests/test_termcolors.py (renamed from tests/regressiontests/utils/termcolors.py) | 11 | ||||
| -rw-r--r-- | tests/utils_tests/test_text.py (renamed from tests/regressiontests/utils/text.py) | 51 | ||||
| -rw-r--r-- | tests/utils_tests/test_timesince.py (renamed from tests/regressiontests/utils/timesince.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/test_timezone.py (renamed from tests/regressiontests/utils/timezone.py) | 30 | ||||
| -rw-r--r-- | tests/utils_tests/test_tzinfo.py (renamed from tests/regressiontests/utils/tzinfo.py) | 0 | ||||
| -rw-r--r-- | tests/utils_tests/tests.py | 34 | ||||
| -rw-r--r-- | tests/validation/__init__.py (renamed from tests/modeltests/validation/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/validation/models.py (renamed from tests/modeltests/validation/models.py) | 0 | ||||
| -rw-r--r-- | tests/validation/test_custom_messages.py (renamed from tests/modeltests/validation/test_custom_messages.py) | 0 | ||||
| -rw-r--r-- | tests/validation/test_error_messages.py (renamed from tests/modeltests/validation/test_error_messages.py) | 0 | ||||
| -rw-r--r-- | tests/validation/test_unique.py (renamed from tests/modeltests/validation/test_unique.py) | 0 | ||||
| -rw-r--r-- | tests/validation/test_validators.py (renamed from tests/modeltests/validation/validators.py) | 0 | ||||
| -rw-r--r-- | tests/validation/tests.py (renamed from tests/modeltests/validation/tests.py) | 4 | ||||
| -rw-r--r-- | tests/validators/__init__.py | 0 | ||||
| -rw-r--r-- | tests/validators/models.py | 0 | ||||
| -rw-r--r-- | tests/validators/tests.py (renamed from tests/modeltests/validators/tests.py) | 3 | ||||
| -rw-r--r-- | tests/version/__init__.py | 0 | ||||
| -rw-r--r-- | tests/version/models.py | 0 | ||||
| -rw-r--r-- | tests/version/tests.py (renamed from tests/regressiontests/version/tests.py) | 3 | ||||
| -rw-r--r-- | tests/view_tests/__init__.py (renamed from tests/regressiontests/views/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app0/__init__.py (renamed from tests/regressiontests/views/app0/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app0/locale/en/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mo) | bin | 482 -> 482 bytes | |||
| -rw-r--r-- | tests/view_tests/app0/locale/en/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app1/__init__.py (renamed from tests/regressiontests/views/app1/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app1/locale/fr/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo) | bin | 482 -> 482 bytes | |||
| -rw-r--r-- | tests/view_tests/app1/locale/fr/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app2/__init__.py (renamed from tests/regressiontests/views/app2/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app2/locale/fr/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo) | bin | 482 -> 482 bytes | |||
| -rw-r--r-- | tests/view_tests/app2/locale/fr/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app3/__init__.py (renamed from tests/regressiontests/views/app3/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app3/locale/es_AR/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo) | bin | 483 -> 483 bytes | |||
| -rw-r--r-- | tests/view_tests/app3/locale/es_AR/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app4/__init__.py (renamed from tests/regressiontests/views/app4/__init__.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/app4/locale/es_AR/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo) | bin | 483 -> 483 bytes | |||
| -rw-r--r-- | tests/view_tests/app4/locale/es_AR/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/fixtures/testdata.json (renamed from tests/regressiontests/views/fixtures/testdata.json) | 24 | ||||
| -rw-r--r-- | tests/view_tests/generic_urls.py (renamed from tests/regressiontests/views/generic_urls.py) | 8 | ||||
| -rw-r--r-- | tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mo) | bin | 615 -> 615 bytes | |||
| -rw-r--r-- | tests/view_tests/locale/de/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/locale/es/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo) | bin | 490 -> 490 bytes | |||
| -rw-r--r-- | tests/view_tests/locale/es/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/locale/fr/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo) | bin | 519 -> 519 bytes | |||
| -rw-r--r-- | tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo (renamed from tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo) | bin | 489 -> 489 bytes | |||
| -rw-r--r-- | tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po (renamed from tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po) | 0 | ||||
| -rw-r--r-- | tests/view_tests/media/file.txt (renamed from tests/regressiontests/views/media/file.txt) | 0 | ||||
| -rw-r--r-- | tests/view_tests/media/file.txt.gz (renamed from tests/regressiontests/views/media/file.txt.gz) | bin | 51 -> 51 bytes | |||
| -rw-r--r-- | tests/view_tests/media/file.unknown (renamed from tests/regressiontests/views/media/file.unknown) | 0 | ||||
| -rw-r--r-- | tests/view_tests/models.py (renamed from tests/regressiontests/views/models.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/templates/debug/render_test.html (renamed from tests/regressiontests/views/templates/debug/render_test.html) | 0 | ||||
| -rw-r--r-- | tests/view_tests/templates/debug/template_exception.html (renamed from tests/regressiontests/views/templates/debug/template_exception.html) | 0 | ||||
| -rw-r--r-- | tests/view_tests/templates/jsi18n.html (renamed from tests/regressiontests/views/templates/jsi18n.html) | 0 | ||||
| -rw-r--r-- | tests/view_tests/templatetags/__init__.py | 0 | ||||
| -rw-r--r-- | tests/view_tests/templatetags/debugtags.py (renamed from tests/regressiontests/views/templatetags/debugtags.py) | 0 | ||||
| -rw-r--r-- | tests/view_tests/tests/__init__.py | 10 | ||||
| -rw-r--r-- | tests/view_tests/tests/test_debug.py (renamed from tests/regressiontests/views/tests/debug.py) | 94 | ||||
| -rw-r--r-- | tests/view_tests/tests/test_defaults.py (renamed from tests/regressiontests/views/tests/defaults.py) | 37 | ||||
| -rw-r--r-- | tests/view_tests/tests/test_i18n.py (renamed from tests/regressiontests/views/tests/i18n.py) | 25 | ||||
| -rw-r--r-- | tests/view_tests/tests/test_shortcuts.py (renamed from tests/regressiontests/views/tests/shortcuts.py) | 6 | ||||
| -rw-r--r-- | tests/view_tests/tests/test_specials.py (renamed from tests/regressiontests/views/tests/specials.py) | 2 | ||||
| -rw-r--r-- | tests/view_tests/tests/test_static.py (renamed from tests/regressiontests/views/tests/static.py) | 6 | ||||
| -rw-r--r-- | tests/view_tests/urls.py (renamed from tests/regressiontests/views/urls.py) | 13 | ||||
| -rw-r--r-- | tests/view_tests/views.py (renamed from tests/regressiontests/views/views.py) | 39 | ||||
| -rw-r--r-- | tests/wsgi/__init__.py | 0 | ||||
| -rw-r--r-- | tests/wsgi/models.py | 0 | ||||
| -rw-r--r-- | tests/wsgi/tests.py (renamed from tests/regressiontests/wsgi/tests.py) | 20 | ||||
| -rw-r--r-- | tests/wsgi/urls.py (renamed from tests/regressiontests/wsgi/urls.py) | 0 | ||||
| -rw-r--r-- | tests/wsgi/wsgi.py (renamed from tests/regressiontests/wsgi/wsgi.py) | 0 |
1111 files changed, 11343 insertions, 4144 deletions
diff --git a/tests/.coveragerc b/tests/.coveragerc index b979e94c58..cd7fee3c89 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -1,5 +1,5 @@ [run] -omit = runtests,test_sqlite,regressiontests*,modeltests*,*/django/contrib/*/tests*,*/django/utils/unittest*,*/django/utils/simplejson*,*/django/utils/importlib.py,*/django/test/_doctest.py,*/django/core/servers/fastcgi.py,*/django/utils/autoreload.py,*/django/utils/dictconfig.py +omit = */django/contrib/*/tests*,*/django/utils/unittest*,*/django/utils/simplejson*,*/django/utils/importlib.py,*/django/test/_doctest.py,*/django/core/servers/fastcgi.py,*/django/utils/autoreload.py,*/django/utils/dictconfig.py [html] directory = coverage_html diff --git a/tests/modeltests/__init__.py b/tests/admin_changelist/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/__init__.py +++ b/tests/admin_changelist/__init__.py diff --git a/tests/regressiontests/admin_changelist/admin.py b/tests/admin_changelist/admin.py index 5751d04bce..8387ba77a1 100644 --- a/tests/regressiontests/admin_changelist/admin.py +++ b/tests/admin_changelist/admin.py @@ -34,8 +34,8 @@ class ChildAdmin(admin.ModelAdmin): list_per_page = 10 list_filter = ['parent', 'age'] - def queryset(self, request): - return super(ChildAdmin, self).queryset(request).select_related("parent__name") + def get_queryset(self, request): + return super(ChildAdmin, self).get_queryset(request).select_related("parent__name") class CustomPaginationAdmin(ChildAdmin): @@ -46,8 +46,8 @@ class FilteredChildAdmin(admin.ModelAdmin): list_display = ['name', 'parent'] list_per_page = 10 - def queryset(self, request): - return super(FilteredChildAdmin, self).queryset(request).filter( + def get_queryset(self, request): + return super(FilteredChildAdmin, self).get_queryset(request).filter( name__contains='filtered') diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/admin_changelist/models.py index 4ba2f9c503..242e4b7b6c 100644 --- a/tests/regressiontests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -74,8 +74,8 @@ class UnorderedObject(models.Model): class OrderedObjectManager(models.Manager): - def get_query_set(self): - return super(OrderedObjectManager, self).get_query_set().order_by('number') + def get_queryset(self): + return super(OrderedObjectManager, self).get_queryset().order_by('number') class OrderedObject(models.Model): """ @@ -87,3 +87,6 @@ class OrderedObject(models.Model): number = models.IntegerField(default=0, db_column='number_val') objects = OrderedObjectManager() + +class CustomIdUser(models.Model): + uuid = models.AutoField(primary_key=True) diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index e8d4cbff16..fd433967d3 100644 --- a/tests/regressiontests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -20,11 +20,11 @@ from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin, SwallowAdmin, DynamicListFilterChildAdmin) from .models import (Event, Child, Parent, Genre, Band, Musician, Group, Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow, - UnorderedObject, OrderedObject) + UnorderedObject, OrderedObject, CustomIdUser) class ChangeListTests(TestCase): - urls = "regressiontests.admin_changelist.urls" + urls = "admin_changelist.urls" def setUp(self): self.factory = RequestFactory() @@ -39,15 +39,15 @@ class ChangeListTests(TestCase): 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(). + Regression test for #10348: ChangeList.get_queryset() shouldn't + overwrite a custom select_related provided by ModelAdmin.get_queryset(). """ m = ChildAdmin(Child, admin.site) request = self.factory.get('/child/') 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_max_show_all, m.list_editable, m) - self.assertEqual(cl.query_set.query.select_related, {'parent': {'name': {}}}) + self.assertEqual(cl.queryset.query.select_related, {'parent': {'name': {}}}) def test_result_list_empty_changelist_value(self): """ @@ -122,12 +122,11 @@ class ChangeListTests(TestCase): 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="%d" id="id_form-0-id" /></div>' % new_child.id - self.assertFalse(table_output.find(hiddenfields_div) == -1, - 'Failed to find hidden fields in: %s' % table_output) + self.assertInHTML(hiddenfields_div, table_output, msg_prefix='Failed to find hidden fields') + # 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) + self.assertInHTML('<td>%s</td>' % editable_name_field, table_output, msg_prefix='Failed to find "name" list_editable field') def test_result_list_editable(self): """ @@ -278,7 +277,7 @@ class ChangeListTests(TestCase): m.list_max_show_all, m.list_editable, m) # Make sure distinct() was called - self.assertEqual(cl.query_set.count(), 1) + self.assertEqual(cl.queryset.count(), 1) def test_distinct_for_non_unique_related_object_in_search_fields(self): """ @@ -298,7 +297,7 @@ class ChangeListTests(TestCase): m.list_max_show_all, m.list_editable, m) # Make sure distinct() was called - self.assertEqual(cl.query_set.count(), 1) + self.assertEqual(cl.queryset.count(), 1) def test_pagination(self): """ @@ -318,7 +317,7 @@ class ChangeListTests(TestCase): m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) - self.assertEqual(cl.query_set.count(), 60) + self.assertEqual(cl.queryset.count(), 60) self.assertEqual(cl.paginator.count, 60) self.assertEqual(list(cl.paginator.page_range), [1, 2, 3, 4, 5, 6]) @@ -328,7 +327,7 @@ class ChangeListTests(TestCase): m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) - self.assertEqual(cl.query_set.count(), 30) + self.assertEqual(cl.queryset.count(), 30) self.assertEqual(cl.paginator.count, 30) self.assertEqual(list(cl.paginator.page_range), [1, 2, 3]) @@ -564,3 +563,22 @@ class ChangeListTests(TestCase): request = self._mocked_authenticated_request('/child/', user_parents) response = m.changelist_view(request) self.assertEqual(response.context_data['cl'].list_filter, ('parent', 'name', 'age')) + +class AdminLogNodeTestCase(TestCase): + + def test_get_admin_log_templatetag_custom_user(self): + """ + Regression test for ticket #20088: admin log depends on User model + having id field as primary key. + + The old implementation raised an AttributeError when trying to use + the id field. + """ + context = Context({'user': CustomIdUser()}) + template_string = '{% load log %}{% get_admin_log 10 as admin_log for_user user %}' + + template = Template(template_string) + + # Rendering should be u'' since this templatetag just logs, + # it doesn't render any string. + self.assertEqual(template.render(context), '') diff --git a/tests/regressiontests/admin_changelist/urls.py b/tests/admin_changelist/urls.py index a5d927e20b..a5d927e20b 100644 --- a/tests/regressiontests/admin_changelist/urls.py +++ b/tests/admin_changelist/urls.py diff --git a/tests/modeltests/distinct_on_fields/__init__.py b/tests/admin_custom_urls/__init__.py index 792d600548..792d600548 100644 --- a/tests/modeltests/distinct_on_fields/__init__.py +++ b/tests/admin_custom_urls/__init__.py diff --git a/tests/regressiontests/admin_custom_urls/fixtures/actions.json b/tests/admin_custom_urls/fixtures/actions.json index 7c6341d71d..7c6341d71d 100644 --- a/tests/regressiontests/admin_custom_urls/fixtures/actions.json +++ b/tests/admin_custom_urls/fixtures/actions.json diff --git a/tests/regressiontests/admin_custom_urls/fixtures/users.json b/tests/admin_custom_urls/fixtures/users.json index 72d86d70ad..72d86d70ad 100644 --- a/tests/regressiontests/admin_custom_urls/fixtures/users.json +++ b/tests/admin_custom_urls/fixtures/users.json diff --git a/tests/regressiontests/admin_custom_urls/models.py b/tests/admin_custom_urls/models.py index b9b3285463..55fc064835 100644 --- a/tests/regressiontests/admin_custom_urls/models.py +++ b/tests/admin_custom_urls/models.py @@ -1,7 +1,9 @@ from functools import update_wrapper from django.contrib import admin +from django.core.urlresolvers import reverse from django.db import models +from django.http import HttpResponseRedirect from django.utils.encoding import python_2_unicode_compatible @@ -40,7 +42,7 @@ class ActionAdmin(admin.ModelAdmin): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) - info = self.model._meta.app_label, self.model._meta.module_name + info = self.model._meta.app_label, self.model._meta.model_name view_name = '%s_%s_add' % info @@ -49,41 +51,30 @@ class ActionAdmin(admin.ModelAdmin): ) + self.remove_url(view_name) -admin.site.register(Action, ActionAdmin) - - class Person(models.Model): - nick = models.CharField(max_length=20) - + name = models.CharField(max_length=20) class PersonAdmin(admin.ModelAdmin): - """A custom ModelAdmin that customizes the deprecated post_url_continue - argument to response_add()""" - def response_add(self, request, obj, post_url_continue='../%s/continue/', - continue_url=None, add_url=None, hasperm_url=None, - noperm_url=None): - return super(PersonAdmin, self).response_add(request, obj, - post_url_continue, - continue_url, add_url, - hasperm_url, noperm_url) + def response_post_save_add(self, request, obj): + return HttpResponseRedirect( + reverse('admin:admin_custom_urls_person_history', args=[obj.pk])) -admin.site.register(Person, PersonAdmin) + def response_post_save_change(self, request, obj): + return HttpResponseRedirect( + reverse('admin:admin_custom_urls_person_delete', args=[obj.pk])) -class City(models.Model): +class Car(models.Model): name = models.CharField(max_length=20) +class CarAdmin(admin.ModelAdmin): -class CityAdmin(admin.ModelAdmin): - """A custom ModelAdmin that redirects to the changelist when the user - presses the 'Save and add another' button when adding a model instance.""" - def response_add(self, request, obj, - add_another_url='admin:admin_custom_urls_city_changelist', - **kwargs): - return super(CityAdmin, self).response_add(request, obj, - add_another_url=add_another_url, - **kwargs) + def response_add(self, request, obj, post_url_continue=None): + return super(CarAdmin, self).response_add( + request, obj, post_url_continue=reverse('admin:admin_custom_urls_car_history', args=[obj.pk])) -admin.site.register(City, CityAdmin) +admin.site.register(Action, ActionAdmin) +admin.site.register(Person, PersonAdmin) +admin.site.register(Car, CarAdmin) diff --git a/tests/regressiontests/admin_custom_urls/tests.py b/tests/admin_custom_urls/tests.py index 87c72e2e71..31c93410f4 100644 --- a/tests/regressiontests/admin_custom_urls/tests.py +++ b/tests/admin_custom_urls/tests.py @@ -1,5 +1,4 @@ from __future__ import absolute_import, unicode_literals - import warnings from django.contrib.admin.util import quote @@ -8,7 +7,7 @@ from django.template.response import TemplateResponse from django.test import TestCase from django.test.utils import override_settings -from .models import Action, Person, City +from .models import Action, Person, Car @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @@ -86,8 +85,8 @@ class AdminCustomUrlsTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class CustomUrlsWorkflowTests(TestCase): - fixtures = ['users.json'] +class CustomRedirects(TestCase): + fixtures = ['users.json', 'actions.json'] def setUp(self): self.client.login(username='super', password='secret') @@ -95,33 +94,48 @@ class CustomUrlsWorkflowTests(TestCase): def tearDown(self): self.client.logout() - def test_old_argument_deprecation(self): - """Test reporting of post_url_continue deprecation.""" - post_data = { - 'nick': 'johndoe', - } - cnt = Person.objects.count() - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - response = self.client.post(reverse('admin:admin_custom_urls_person_add'), post_data) - self.assertEqual(response.status_code, 302) - self.assertEqual(Person.objects.count(), cnt + 1) - # We should get a DeprecationWarning - self.assertEqual(len(w), 1) - self.assertTrue(isinstance(w[0].message, DeprecationWarning)) + def test_post_save_add_redirect(self): + """ + Ensures that ModelAdmin.response_post_save_add() controls the + redirection after the 'Save' button has been pressed when adding a + new object. + Refs 8001, 18310, 19505. + """ + post_data = { 'name': 'John Doe', } + self.assertEqual(Person.objects.count(), 0) + response = self.client.post( + reverse('admin:admin_custom_urls_person_add'), post_data) + persons = Person.objects.all() + self.assertEqual(len(persons), 1) + self.assertRedirects( + response, reverse('admin:admin_custom_urls_person_history', args=[persons[0].pk])) - def test_custom_add_another_redirect(self): - """Test customizability of post-object-creation redirect URL.""" - post_data = { - 'name': 'Rome', - '_addanother': '1', - } - cnt = City.objects.count() - with warnings.catch_warnings(record=True) as w: - # POST to the view whose post-object-creation redir URL argument we - # are customizing (object creation) - response = self.client.post(reverse('admin:admin_custom_urls_city_add'), post_data) - self.assertEqual(City.objects.count(), cnt + 1) - # Check that it redirected to the URL we set - self.assertRedirects(response, reverse('admin:admin_custom_urls_city_changelist')) - self.assertEqual(len(w), 0) # We should get no DeprecationWarning + def test_post_save_change_redirect(self): + """ + Ensures that ModelAdmin.response_post_save_change() controls the + redirection after the 'Save' button has been pressed when editing an + existing object. + Refs 8001, 18310, 19505. + """ + Person.objects.create(name='John Doe') + self.assertEqual(Person.objects.count(), 1) + person = Person.objects.all()[0] + post_data = { 'name': 'Jack Doe', } + response = self.client.post( + reverse('admin:admin_custom_urls_person_change', args=[person.pk]), post_data) + self.assertRedirects( + response, reverse('admin:admin_custom_urls_person_delete', args=[person.pk])) + + def test_post_url_continue(self): + """ + Ensures that the ModelAdmin.response_add()'s parameter `post_url_continue` + controls the redirection after an object has been created. + """ + post_data = { 'name': 'SuperFast', '_continue': '1' } + self.assertEqual(Car.objects.count(), 0) + response = self.client.post( + reverse('admin:admin_custom_urls_car_add'), post_data) + cars = Car.objects.all() + self.assertEqual(len(cars), 1) + self.assertRedirects( + response, reverse('admin:admin_custom_urls_car_history', args=[cars[0].pk])) diff --git a/tests/regressiontests/admin_custom_urls/urls.py b/tests/admin_custom_urls/urls.py index 478390737d..478390737d 100644 --- a/tests/regressiontests/admin_custom_urls/urls.py +++ b/tests/admin_custom_urls/urls.py diff --git a/tests/modeltests/aggregation/__init__.py b/tests/admin_filters/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/aggregation/__init__.py +++ b/tests/admin_filters/__init__.py diff --git a/tests/regressiontests/admin_filters/models.py b/tests/admin_filters/models.py index e0b8bde2de..e0b8bde2de 100644 --- a/tests/regressiontests/admin_filters/models.py +++ b/tests/admin_filters/models.py diff --git a/tests/regressiontests/admin_filters/tests.py b/tests/admin_filters/tests.py index 4a3e6135c3..f05e8e2011 100644 --- a/tests/regressiontests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -61,7 +61,7 @@ class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter) class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter): def lookups(self, request, model_admin): - qs = model_admin.queryset(request) + qs = model_admin.get_queryset(request) if qs.filter(year__gte=1980, year__lte=1989).exists(): yield ('the 80s', "the 1980's") if qs.filter(year__gte=1990, year__lte=1999).exists(): @@ -83,11 +83,11 @@ class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter): parameter_name = 'department' def lookups(self, request, model_admin): - return set([ + return sorted(set([ (employee.department.id, # Intentionally not a string (Refs #19318) employee.department.code) - for employee in model_admin.queryset(request).all() - ]) + for employee in model_admin.get_queryset(request).all() + ])) def queryset(self, request, queryset): if self.value(): @@ -183,7 +183,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book]) # Make sure the correct choice is selected @@ -200,7 +200,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) if (self.today.year, self.today.month) == (self.one_week_ago.year, self.one_week_ago.month): # In case one week ago is in the same month. self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) @@ -221,7 +221,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) if self.today.year == self.one_week_ago.year: # In case one week ago is in the same year. self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) @@ -242,7 +242,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book]) # Make sure the correct choice is selected @@ -266,7 +266,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book]) # Make sure the last choice is None and is selected @@ -293,7 +293,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.gipsy_book]) # Make sure the last choice is None and is selected @@ -321,7 +321,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book, self.bio_book, self.djangonaut_book]) # Make sure the last choice is None and is selected @@ -349,7 +349,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, User, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.lisa]) # Make sure the last choice is None and is selected @@ -374,7 +374,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, User, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.alfred]) # Make sure the last choice is None and is selected @@ -410,7 +410,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected @@ -424,7 +424,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) # Make sure the correct choice is selected @@ -438,7 +438,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.django_book]) # Make sure the correct choice is selected @@ -457,7 +457,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), list(Book.objects.all().order_by('-id'))) # Make sure the correct choice is selected @@ -474,7 +474,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), []) # Make sure the correct choice is selected @@ -491,7 +491,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected @@ -508,7 +508,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book]) # Make sure the correct choice is selected @@ -525,7 +525,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.djangonaut_book]) # Make sure the correct choices are selected @@ -534,13 +534,13 @@ class ListFiltersTests(TestCase): choices = list(filterspec.choices(changelist)) self.assertEqual(choices[3]['display'], 'the 2000\'s') self.assertEqual(choices[3]['selected'], True) - self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) + self.assertEqual(choices[3]['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk) filterspec = changelist.get_filters(request)[0][0] self.assertEqual(force_text(filterspec.title), 'Verbose Author') choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertEqual(choice['selected'], True) - self.assertEqual(choice['query_string'], '?publication-decade=the+00s&author__id__exact=%s' % self.alfred.pk) + self.assertEqual(choice['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk) def test_listfilter_without_title(self): """ @@ -615,7 +615,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) filterspec = changelist.get_filters(request)[0][-1] @@ -637,7 +637,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected @@ -654,7 +654,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Book, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book]) # Make sure the correct choice is selected @@ -676,16 +676,16 @@ class ListFiltersTests(TestCase): request = self.request_factory.get('/', {'department': self.john.pk}) changelist = self.get_changelist(request, Employee, modeladmin) - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] self.assertEqual(force_text(filterspec.title), 'department') choices = list(filterspec.choices(changelist)) - self.assertEqual(choices[2]['display'], 'DEV') - self.assertEqual(choices[2]['selected'], True) - self.assertEqual(choices[2]['query_string'], '?department=%s' % self.john.pk) + self.assertEqual(choices[1]['display'], 'DEV') + self.assertEqual(choices[1]['selected'], True) + self.assertEqual(choices[1]['query_string'], '?department=%s' % self.john.pk) def test_fk_with_to_field(self): """ @@ -698,7 +698,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Employee, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.jack, self.john]) filterspec = changelist.get_filters(request)[0][-1] @@ -723,7 +723,7 @@ class ListFiltersTests(TestCase): changelist = self.get_changelist(request, Employee, modeladmin) # Make sure the correct queryset is returned - queryset = changelist.get_query_set(request) + queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.john]) filterspec = changelist.get_filters(request)[0][-1] diff --git a/tests/modeltests/basic/__init__.py b/tests/admin_inlines/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/basic/__init__.py +++ b/tests/admin_inlines/__init__.py diff --git a/tests/regressiontests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index cf51fa49b4..44671d0ac4 100644 --- a/tests/regressiontests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -28,6 +28,11 @@ class HolderAdmin(admin.ModelAdmin): js = ('my_awesome_admin_scripts.js',) +class ReadOnlyInlineInline(admin.TabularInline): + model = ReadOnlyInline + readonly_fields = ['name'] + + class InnerInline2(admin.StackedInline): model = Inner2 @@ -124,6 +129,9 @@ class ChildModel1Inline(admin.TabularInline): class ChildModel2Inline(admin.StackedInline): model = ChildModel2 +# admin for #19524 +class SightingInline(admin.TabularInline): + model = Sighting site.register(TitleCollection, inlines=[TitleInline]) # Test bug #12561 and #12778 @@ -139,6 +147,7 @@ site.register(Novel, NovelAdmin) site.register(Fashionista, inlines=[InlineWeakness]) site.register(Holder4, Holder4Admin) site.register(Author, AuthorAdmin) -site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline]) +site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline]) site.register(ProfileCollection, inlines=[ProfileInline]) -site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
\ No newline at end of file +site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline]) +site.register(ExtraTerrestrial, inlines=[SightingInline]) diff --git a/tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml b/tests/admin_inlines/fixtures/admin-views-users.xml index aba8f4aace..aba8f4aace 100644 --- a/tests/regressiontests/admin_inlines/fixtures/admin-views-users.xml +++ b/tests/admin_inlines/fixtures/admin-views-users.xml diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/admin_inlines/models.py index b004d5f85a..82c1c3f078 100644 --- a/tests/regressiontests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -90,7 +90,6 @@ class Inner4Tabular(models.Model): dummy = models.IntegerField(help_text="Awesome tabular help text is awesome.") holder = models.ForeignKey(Holder4) - # Models for #12749 class Person(models.Model): @@ -129,16 +128,24 @@ class Novel(models.Model): name = models.CharField(max_length=40) class Chapter(models.Model): + name = models.CharField(max_length=40) novel = models.ForeignKey(Novel) +class FootNote(models.Model): + """ + Model added for ticket 19838 + """ + chapter = models.ForeignKey(Chapter, on_delete=models.PROTECT) + note = models.CharField(max_length=40) # Models for #16838 + class CapoFamiglia(models.Model): name = models.CharField(max_length=100) class Consigliere(models.Model): - name = models.CharField(max_length=100) + name = models.CharField(max_length=100, help_text='Help text for Consigliere') capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+') @@ -146,6 +153,12 @@ class SottoCapo(models.Model): name = models.CharField(max_length=100) capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+') + +class ReadOnlyInline(models.Model): + name = models.CharField(max_length=100, help_text='Help text for ReadOnlyInline') + capo_famiglia = models.ForeignKey(CapoFamiglia) + + # Models for #18433 class ParentModelWithCustomPk(models.Model): @@ -170,6 +183,17 @@ class ChildModel2(models.Model): def get_absolute_url(self): return '/child_model2/' +# Models for #19524 + +class LifeForm(models.Model): + pass + +class ExtraTerrestrial(LifeForm): + name = models.CharField(max_length=100) + +class Sighting(models.Model): + et = models.ForeignKey(ExtraTerrestrial) + place = models.CharField(max_length=100) # Other models diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 5bb6077bff..714a2f1c61 100644 --- a/tests/regressiontests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -12,12 +12,12 @@ from .admin import InnerInline, TitleInline, site from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile, ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2, - Title) + Sighting, Title, Novel, Chapter, FootNote) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class TestInline(TestCase): - urls = "regressiontests.admin_inlines.urls" + urls = "admin_inlines.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -122,6 +122,10 @@ class TestInline(TestCase): response = self.client.get('/admin/admin_inlines/holder4/add/') self.assertContains(response, '<p class="help">Awesome stacked help text is awesome.</p>', 4) self.assertContains(response, '<img src="/static/admin/img/icon-unknown.gif" class="help help-tooltip" width="10" height="10" alt="(Awesome tabular help text is awesome.)" title="Awesome tabular help text is awesome." />', 1) + # ReadOnly fields + response = self.client.get('/admin/admin_inlines/capofamiglia/add/') + self.assertContains(response, '<img src="/static/admin/img/icon-unknown.gif" class="help help-tooltip" width="10" height="10" alt="(Help text for ReadOnlyInline)" title="Help text for ReadOnlyInline" />', 1) + def test_non_related_name_inline(self): """ @@ -131,19 +135,17 @@ class TestInline(TestCase): response = self.client.get('/admin/admin_inlines/capofamiglia/add/') self.assertContains(response, - '<input type="hidden" name="-1-0-id" id="id_-1-0-id" />') + '<input type="hidden" name="-1-0-id" id="id_-1-0-id" />', html=True) self.assertContains(response, - '<input type="hidden" name="-1-0-capo_famiglia" ' - 'id="id_-1-0-capo_famiglia" />') + '<input type="hidden" name="-1-0-capo_famiglia" id="id_-1-0-capo_famiglia" />', html=True) self.assertContains(response, '<input id="id_-1-0-name" type="text" class="vTextField" ' 'name="-1-0-name" maxlength="100" />', html=True) self.assertContains(response, - '<input type="hidden" name="-2-0-id" id="id_-2-0-id" />') + '<input type="hidden" name="-2-0-id" id="id_-2-0-id" />', html=True) self.assertContains(response, - '<input type="hidden" name="-2-0-capo_famiglia" ' - 'id="id_-2-0-capo_famiglia" />') + '<input type="hidden" name="-2-0-capo_famiglia" id="id_-2-0-capo_famiglia" />', html=True) self.assertContains(response, '<input id="id_-2-0-name" type="text" class="vTextField" ' 'name="-2-0-name" maxlength="100" />', html=True) @@ -174,10 +176,27 @@ class TestInline(TestCase): self.assertContains(response, child1_shortcut) self.assertContains(response, child2_shortcut) + def test_create_inlines_on_inherited_model(self): + """ + Ensure that an object can be created with inlines when it inherits + another class. Bug #19524. + """ + data = { + 'name': 'Martian', + 'sighting_set-TOTAL_FORMS': 1, + 'sighting_set-INITIAL_FORMS': 0, + 'sighting_set-MAX_NUM_FORMS': 0, + 'sighting_set-0-place': 'Zone 51', + '_save': 'Save', + } + response = self.client.post('/admin/admin_inlines/extraterrestrial/add/', data) + self.assertEqual(response.status_code, 302) + self.assertEqual(Sighting.objects.filter(et__name='Martian').count(), 1) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class TestInlineMedia(TestCase): - urls = "regressiontests.admin_inlines.urls" + urls = "admin_inlines.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -214,7 +233,7 @@ class TestInlineMedia(TestCase): self.assertContains(response, 'my_awesome_inline_scripts.js') class TestInlineAdminForm(TestCase): - urls = "regressiontests.admin_inlines.urls" + urls = "admin_inlines.urls" def test_immutable_content_type(self): """Regression for #9362 @@ -231,13 +250,51 @@ class TestInlineAdminForm(TestCase): parent_ct = ContentType.objects.get_for_model(Parent) self.assertEqual(iaf.original.content_type, parent_ct) + +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) +class TestInlineProtectedOnDelete(TestCase): + urls = "admin_inlines.urls" + 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_deleting_inline_with_protected_delete_does_not_validate(self): + lotr = Novel.objects.create(name='Lord of the rings') + chapter = Chapter.objects.create(novel=lotr, name='Many Meetings') + foot_note = FootNote.objects.create(chapter=chapter, note='yadda yadda') + + change_url = '/admin/admin_inlines/novel/%i/' % lotr.id + response = self.client.get(change_url) + data = { + 'name': lotr.name, + 'chapter_set-TOTAL_FORMS': 1, + 'chapter_set-INITIAL_FORMS': 1, + 'chapter_set-MAX_NUM_FORMS': 1000, + '_save': 'Save', + 'chapter_set-0-id': chapter.id, + 'chapter_set-0-name': chapter.name, + 'chapter_set-0-novel': lotr.id, + 'chapter_set-0-DELETE': 'on' + } + response = self.client.post(change_url, data) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Deleting chapter %s would require deleting " + "the following protected related objects: foot note %s" + % (chapter, foot_note)) + + class TestInlinePermissions(TestCase): """ Make sure the admin respects permissions for objects that are edited inline. Refs #8060. """ - urls = "regressiontests.admin_inlines.urls" + urls = "admin_inlines.urls" def setUp(self): self.user = User(username='admin') @@ -326,7 +383,8 @@ class TestInlinePermissions(TestCase): # Add permission on inner2s, so we get the inline self.assertContains(response, '<h2>Inner2s</h2>') self.assertContains(response, 'Add another Inner2') - self.assertContains(response, 'value="3" id="id_inner2_set-TOTAL_FORMS"') + self.assertContains(response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" ' + 'value="3" name="inner2_set-TOTAL_FORMS" />', html=True) def test_inline_change_m2m_add_perm(self): permission = Permission.objects.get(codename='add_book', content_type=self.book_ct) @@ -345,8 +403,10 @@ class TestInlinePermissions(TestCase): # We have change perm on books, so we can add/change/delete inlines self.assertContains(response, '<h2>Author-book relationships</h2>') self.assertContains(response, 'Add another Author-Book Relationship') - self.assertContains(response, 'value="4" id="id_Author_books-TOTAL_FORMS"') - self.assertContains(response, '<input type="hidden" name="Author_books-0-id" value="%i"' % self.author_book_auto_m2m_intermediate_id) + self.assertContains(response, '<input type="hidden" id="id_Author_books-TOTAL_FORMS" ' + 'value="4" name="Author_books-TOTAL_FORMS" />', html=True) + self.assertContains(response, '<input type="hidden" id="id_Author_books-0-id" ' + 'value="%i" name="Author_books-0-id" />' % self.author_book_auto_m2m_intermediate_id, html=True) self.assertContains(response, 'id="id_Author_books-0-DELETE"') def test_inline_change_fk_add_perm(self): @@ -357,8 +417,10 @@ class TestInlinePermissions(TestCase): self.assertContains(response, '<h2>Inner2s</h2>') self.assertContains(response, 'Add another Inner2') # 3 extra forms only, not the existing instance form - self.assertContains(response, 'value="3" id="id_inner2_set-TOTAL_FORMS"') - self.assertNotContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" ' + 'value="3" name="inner2_set-TOTAL_FORMS" />', html=True) + self.assertNotContains(response, '<input type="hidden" id="id_inner2_set-0-id" ' + 'value="%i" name="inner2_set-0-id" />' % self.inner2_id, html=True) def test_inline_change_fk_change_perm(self): permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct) @@ -367,10 +429,13 @@ class TestInlinePermissions(TestCase): # Change permission on inner2s, so we can change existing but not add new self.assertContains(response, '<h2>Inner2s</h2>') # Just the one form for existing instances - self.assertContains(response, 'value="1" id="id_inner2_set-TOTAL_FORMS"') - self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" ' + 'value="1" name="inner2_set-TOTAL_FORMS" />', html=True) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-0-id" ' + 'value="%i" name="inner2_set-0-id" />' % self.inner2_id, html=True) # max-num 0 means we can't add new ones - self.assertContains(response, 'value="0" id="id_inner2_set-MAX_NUM_FORMS"') + self.assertContains(response, '<input type="hidden" id="id_inner2_set-MAX_NUM_FORMS" ' + 'value="0" name="inner2_set-MAX_NUM_FORMS" />', html=True) def test_inline_change_fk_add_change_perm(self): permission = Permission.objects.get(codename='add_inner2', content_type=self.inner_ct) @@ -381,9 +446,10 @@ class TestInlinePermissions(TestCase): # Add/change perm, so we can add new and change existing self.assertContains(response, '<h2>Inner2s</h2>') # One form for existing instance and three extra for new - self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"') - self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) - + self.assertContains(response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" ' + 'value="4" name="inner2_set-TOTAL_FORMS" />', html=True) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-0-id" ' + 'value="%i" name="inner2_set-0-id" />' % self.inner2_id, html=True) def test_inline_change_fk_change_del_perm(self): permission = Permission.objects.get(codename='change_inner2', content_type=self.inner_ct) @@ -394,8 +460,10 @@ class TestInlinePermissions(TestCase): # Change/delete perm on inner2s, so we can change/delete existing self.assertContains(response, '<h2>Inner2s</h2>') # One form for existing instance only, no new - self.assertContains(response, 'value="1" id="id_inner2_set-TOTAL_FORMS"') - self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" ' + 'value="1" name="inner2_set-TOTAL_FORMS" />', html=True) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-0-id" ' + 'value="%i" name="inner2_set-0-id" />' % self.inner2_id, html=True) self.assertContains(response, 'id="id_inner2_set-0-DELETE"') @@ -410,8 +478,10 @@ class TestInlinePermissions(TestCase): # All perms on inner2s, so we can add/change/delete self.assertContains(response, '<h2>Inner2s</h2>') # One form for existing instance only, three for new - self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"') - self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-TOTAL_FORMS" ' + 'value="4" name="inner2_set-TOTAL_FORMS" />', html=True) + self.assertContains(response, '<input type="hidden" id="id_inner2_set-0-id" ' + 'value="%i" name="inner2_set-0-id" />' % self.inner2_id, html=True) self.assertContains(response, 'id="id_inner2_set-0-DELETE"') @@ -419,7 +489,7 @@ class TestInlinePermissions(TestCase): class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' fixtures = ['admin-views-users.xml'] - urls = "regressiontests.admin_inlines.urls" + urls = "admin_inlines.urls" def test_add_stackeds(self): """ @@ -518,15 +588,7 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): self.selenium.find_element_by_name('profile_set-2-last_name').send_keys('2 last name 2') self.selenium.find_element_by_xpath('//input[@value="Save"]').click() - - try: - # Wait for the next page to be loaded. - self.wait_loaded_tag('body') - except TimeoutException: - # IE7 occasionnally returns an error "Internet Explorer cannot - # display the webpage" and doesn't load the next page. We just - # ignore it. - pass + self.wait_page_loaded() # Check that the objects have been created in the database self.assertEqual(ProfileCollection.objects.all().count(), 1) diff --git a/tests/regressiontests/admin_inlines/urls.py b/tests/admin_inlines/urls.py index cf18ef97cf..cf18ef97cf 100644 --- a/tests/regressiontests/admin_inlines/urls.py +++ b/tests/admin_inlines/urls.py diff --git a/tests/modeltests/choices/__init__.py b/tests/admin_ordering/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/choices/__init__.py +++ b/tests/admin_ordering/__init__.py diff --git a/tests/regressiontests/admin_ordering/models.py b/tests/admin_ordering/models.py index 195b3c3694..3da52b1b00 100644 --- a/tests/regressiontests/admin_ordering/models.py +++ b/tests/admin_ordering/models.py @@ -15,6 +15,7 @@ class Song(models.Model): band = models.ForeignKey(Band) name = models.CharField(max_length=100) duration = models.IntegerField() + other_interpreters = models.ManyToManyField(Band, related_name='covers') class Meta: ordering = ('name',) diff --git a/tests/regressiontests/admin_ordering/tests.py b/tests/admin_ordering/tests.py index faae834f93..6655ad37ad 100644 --- a/tests/regressiontests/admin_ordering/tests.py +++ b/tests/admin_ordering/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals from django.test import TestCase, RequestFactory +from django.contrib import admin from django.contrib.admin.options import ModelAdmin from django.contrib.auth.models import User @@ -21,8 +22,8 @@ request.user = MockSuperUser() 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 + Let's make sure that ModelAdmin.get_queryset uses the ordering we define + in ModelAdmin rather that ordering defined in the model's inner Meta class. """ @@ -41,7 +42,7 @@ class TestAdminOrdering(TestCase): class. """ ma = ModelAdmin(Band, None) - names = [b.name for b in ma.queryset(request)] + names = [b.name for b in ma.get_queryset(request)] self.assertEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) def test_specified_ordering(self): @@ -52,7 +53,7 @@ class TestAdminOrdering(TestCase): class BandAdmin(ModelAdmin): ordering = ('rank',) # default ordering is ('name',) ma = BandAdmin(Band, None) - names = [b.name for b in ma.queryset(request)] + names = [b.name for b in ma.get_queryset(request)] self.assertEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) def test_dynamic_ordering(self): @@ -64,17 +65,17 @@ class TestAdminOrdering(TestCase): request = self.request_factory.get('/') request.user = super_user ma = DynOrderingBandAdmin(Band, None) - names = [b.name for b in ma.queryset(request)] + names = [b.name for b in ma.get_queryset(request)] self.assertEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names) request.user = other_user - names = [b.name for b in ma.queryset(request)] + names = [b.name for b in ma.get_queryset(request)] self.assertEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names) class TestInlineModelAdminOrdering(TestCase): """ - Let's make sure that InlineModelAdmin.queryset uses the ordering we define - in InlineModelAdmin. + Let's make sure that InlineModelAdmin.get_queryset uses the ordering we + define in InlineModelAdmin. """ def setUp(self): @@ -94,7 +95,7 @@ class TestInlineModelAdminOrdering(TestCase): class. """ inline = SongInlineDefaultOrdering(self.b, None) - names = [s.name for s in inline.queryset(request)] + names = [s.name for s in inline.get_queryset(request)] self.assertEqual(['Dude (Looks Like a Lady)', 'Jaded', 'Pink'], names) def test_specified_ordering(self): @@ -102,5 +103,52 @@ class TestInlineModelAdminOrdering(TestCase): Let's check with ordering set to something different than the default. """ inline = SongInlineNewOrdering(self.b, None) - names = [s.name for s in inline.queryset(request)] + names = [s.name for s in inline.get_queryset(request)] self.assertEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names) + + +class TestRelatedFieldsAdminOrdering(TestCase): + def setUp(self): + self.b1 = Band(name='Pink Floyd', bio='', rank=1) + self.b1.save() + self.b2 = Band(name='Foo Fighters', bio='', rank=5) + self.b2.save() + + # we need to register a custom ModelAdmin (instead of just using + # ModelAdmin) because the field creator tries to find the ModelAdmin + # for the related model + class SongAdmin(admin.ModelAdmin): + pass + admin.site.register(Song, SongAdmin) + + def check_ordering_of_field_choices(self, correct_ordering): + fk_field = admin.site._registry[Song].formfield_for_foreignkey(Song.band.field) + m2m_field = admin.site._registry[Song].formfield_for_manytomany(Song.other_interpreters.field) + + self.assertEqual(list(fk_field.queryset), correct_ordering) + self.assertEqual(list(m2m_field.queryset), correct_ordering) + + def test_no_admin_fallback_to_model_ordering(self): + # should be ordered by name (as defined by the model) + self.check_ordering_of_field_choices([self.b2, self.b1]) + + def test_admin_with_no_ordering_fallback_to_model_ordering(self): + class NoOrderingBandAdmin(admin.ModelAdmin): + pass + admin.site.register(Band, NoOrderingBandAdmin) + + # should be ordered by name (as defined by the model) + self.check_ordering_of_field_choices([self.b2, self.b1]) + + def test_admin_ordering_beats_model_ordering(self): + class StaticOrderingBandAdmin(admin.ModelAdmin): + ordering = ('rank', ) + admin.site.register(Band, StaticOrderingBandAdmin) + + # should be ordered by rank (defined by the ModelAdmin) + self.check_ordering_of_field_choices([self.b1, self.b2]) + + def tearDown(self): + admin.site.unregister(Song) + if Band in admin.site._registry: + admin.site.unregister(Band) diff --git a/tests/modeltests/custom_columns/__init__.py b/tests/admin_registration/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/custom_columns/__init__.py +++ b/tests/admin_registration/__init__.py diff --git a/tests/regressiontests/admin_registration/models.py b/tests/admin_registration/models.py index c6f42fd1f4..c6f42fd1f4 100644 --- a/tests/regressiontests/admin_registration/models.py +++ b/tests/admin_registration/models.py diff --git a/tests/regressiontests/admin_registration/tests.py b/tests/admin_registration/tests.py index 1b2d291691..1b2d291691 100644 --- a/tests/regressiontests/admin_registration/tests.py +++ b/tests/admin_registration/tests.py diff --git a/tests/modeltests/custom_managers/__init__.py b/tests/admin_scripts/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/custom_managers/__init__.py +++ b/tests/admin_scripts/__init__.py diff --git a/tests/modeltests/custom_methods/__init__.py b/tests/admin_scripts/app_with_import/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/custom_methods/__init__.py +++ b/tests/admin_scripts/app_with_import/__init__.py diff --git a/tests/regressiontests/admin_scripts/app_with_import/models.py b/tests/admin_scripts/app_with_import/models.py index 17a892bd17..17a892bd17 100644 --- a/tests/regressiontests/admin_scripts/app_with_import/models.py +++ b/tests/admin_scripts/app_with_import/models.py diff --git a/tests/modeltests/custom_pk/__init__.py b/tests/admin_scripts/broken_app/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/custom_pk/__init__.py +++ b/tests/admin_scripts/broken_app/__init__.py diff --git a/tests/regressiontests/admin_scripts/broken_app/models.py b/tests/admin_scripts/broken_app/models.py index f37f1efc11..f37f1efc11 100644 --- a/tests/regressiontests/admin_scripts/broken_app/models.py +++ b/tests/admin_scripts/broken_app/models.py diff --git a/tests/modeltests/defer/__init__.py b/tests/admin_scripts/complex_app/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/defer/__init__.py +++ b/tests/admin_scripts/complex_app/__init__.py diff --git a/tests/modeltests/delete/__init__.py b/tests/admin_scripts/complex_app/admin/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/delete/__init__.py +++ b/tests/admin_scripts/complex_app/admin/__init__.py diff --git a/tests/regressiontests/admin_scripts/complex_app/admin/foo.py b/tests/admin_scripts/complex_app/admin/foo.py index 1ed704a66b..1ed704a66b 100644 --- a/tests/regressiontests/admin_scripts/complex_app/admin/foo.py +++ b/tests/admin_scripts/complex_app/admin/foo.py diff --git a/tests/regressiontests/admin_scripts/complex_app/models/__init__.py b/tests/admin_scripts/complex_app/models/__init__.py index 7b647e74b1..7b647e74b1 100644 --- a/tests/regressiontests/admin_scripts/complex_app/models/__init__.py +++ b/tests/admin_scripts/complex_app/models/__init__.py diff --git a/tests/regressiontests/admin_scripts/complex_app/models/bar.py b/tests/admin_scripts/complex_app/models/bar.py index 15956f7a50..15956f7a50 100644 --- a/tests/regressiontests/admin_scripts/complex_app/models/bar.py +++ b/tests/admin_scripts/complex_app/models/bar.py diff --git a/tests/regressiontests/admin_scripts/complex_app/models/foo.py b/tests/admin_scripts/complex_app/models/foo.py index 914cf3b8b5..914cf3b8b5 100644 --- a/tests/regressiontests/admin_scripts/complex_app/models/foo.py +++ b/tests/admin_scripts/complex_app/models/foo.py diff --git a/tests/modeltests/empty/__init__.py b/tests/admin_scripts/custom_templates/app_template/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/empty/__init__.py +++ b/tests/admin_scripts/custom_templates/app_template/__init__.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/app_template/api.py b/tests/admin_scripts/custom_templates/app_template/api.py index 439d935f73..439d935f73 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/app_template/api.py +++ b/tests/admin_scripts/custom_templates/app_template/api.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/app_template/models.py b/tests/admin_scripts/custom_templates/app_template/models.py index 92be216cfc..92be216cfc 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/app_template/models.py +++ b/tests/admin_scripts/custom_templates/app_template/models.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template.tgz b/tests/admin_scripts/custom_templates/project_template.tgz Binary files differindex 2a181c9faa..2a181c9faa 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template.tgz +++ b/tests/admin_scripts/custom_templates/project_template.tgz diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/Procfile b/tests/admin_scripts/custom_templates/project_template/additional_dir/Procfile index 032f0bcd72..032f0bcd72 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/Procfile +++ b/tests/admin_scripts/custom_templates/project_template/additional_dir/Procfile diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py b/tests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py index 032f0bcd72..032f0bcd72 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py +++ b/tests/admin_scripts/custom_templates/project_template/additional_dir/additional_file.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py b/tests/admin_scripts/custom_templates/project_template/additional_dir/extra.py index 6b553f190f..6b553f190f 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/extra.py +++ b/tests/admin_scripts/custom_templates/project_template/additional_dir/extra.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/requirements.txt b/tests/admin_scripts/custom_templates/project_template/additional_dir/requirements.txt index 032f0bcd72..032f0bcd72 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/additional_dir/requirements.txt +++ b/tests/admin_scripts/custom_templates/project_template/additional_dir/requirements.txt diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/manage.py b/tests/admin_scripts/custom_templates/project_template/manage.py index d9843c433f..d9843c433f 100755 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/manage.py +++ b/tests/admin_scripts/custom_templates/project_template/manage.py diff --git a/tests/modeltests/empty/no_models/__init__.py b/tests/admin_scripts/custom_templates/project_template/project_name/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/empty/no_models/__init__.py +++ b/tests/admin_scripts/custom_templates/project_template/project_name/__init__.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/settings.py b/tests/admin_scripts/custom_templates/project_template/project_name/settings.py index ddc216c829..ddc216c829 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/settings.py +++ b/tests/admin_scripts/custom_templates/project_template/project_name/settings.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt b/tests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt index 873eaded88..873eaded88 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt +++ b/tests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico b/tests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico Binary files differindex 1db49645b5..1db49645b5 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico +++ b/tests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico diff --git a/tests/modeltests/expressions/__init__.py b/tests/admin_scripts/management/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/expressions/__init__.py +++ b/tests/admin_scripts/management/__init__.py diff --git a/tests/modeltests/field_defaults/__init__.py b/tests/admin_scripts/management/commands/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/field_defaults/__init__.py +++ b/tests/admin_scripts/management/commands/__init__.py diff --git a/tests/regressiontests/admin_scripts/management/commands/app_command.py b/tests/admin_scripts/management/commands/app_command.py index d26df12642..d26df12642 100644 --- a/tests/regressiontests/admin_scripts/management/commands/app_command.py +++ b/tests/admin_scripts/management/commands/app_command.py diff --git a/tests/regressiontests/admin_scripts/management/commands/base_command.py b/tests/admin_scripts/management/commands/base_command.py index 2701f32a05..2701f32a05 100644 --- a/tests/regressiontests/admin_scripts/management/commands/base_command.py +++ b/tests/admin_scripts/management/commands/base_command.py diff --git a/tests/regressiontests/admin_scripts/management/commands/custom_startproject.py b/tests/admin_scripts/management/commands/custom_startproject.py index 80c6d6b805..80c6d6b805 100644 --- a/tests/regressiontests/admin_scripts/management/commands/custom_startproject.py +++ b/tests/admin_scripts/management/commands/custom_startproject.py diff --git a/tests/regressiontests/admin_scripts/management/commands/label_command.py b/tests/admin_scripts/management/commands/label_command.py index 3bce1305bc..3bce1305bc 100644 --- a/tests/regressiontests/admin_scripts/management/commands/label_command.py +++ b/tests/admin_scripts/management/commands/label_command.py diff --git a/tests/regressiontests/admin_scripts/management/commands/noargs_command.py b/tests/admin_scripts/management/commands/noargs_command.py index 9652099f9b..9652099f9b 100644 --- a/tests/regressiontests/admin_scripts/management/commands/noargs_command.py +++ b/tests/admin_scripts/management/commands/noargs_command.py diff --git a/tests/regressiontests/admin_scripts/models.py b/tests/admin_scripts/models.py index cd96052098..cd96052098 100644 --- a/tests/regressiontests/admin_scripts/models.py +++ b/tests/admin_scripts/models.py diff --git a/tests/modeltests/field_subclassing/__init__.py b/tests/admin_scripts/simple_app/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/field_subclassing/__init__.py +++ b/tests/admin_scripts/simple_app/__init__.py diff --git a/tests/regressiontests/admin_scripts/simple_app/models.py b/tests/admin_scripts/simple_app/models.py index b89f4b898b..b89f4b898b 100644 --- a/tests/regressiontests/admin_scripts/simple_app/models.py +++ b/tests/admin_scripts/simple_app/models.py diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index d0ca9d26df..baec16820e 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -16,18 +16,26 @@ import codecs from django import conf, bin, get_version from django.conf import settings +from django.core.management import BaseCommand, CommandError from django.db import connection from django.test.simple import DjangoTestSuiteRunner from django.utils import unittest from django.utils.encoding import force_str, force_text from django.utils._os import upath +from django.utils.six import StringIO from django.test import LiveServerTestCase -test_dir = os.path.dirname(os.path.dirname(upath(__file__))) + +test_dir = os.path.realpath(os.path.join(os.environ['DJANGO_TEST_TEMP_DIR'], 'test_project')) +if not os.path.exists(test_dir): + os.mkdir(test_dir) + open(os.path.join(test_dir, '__init__.py'), 'w').close() + +custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates') + class AdminScriptTestCase(unittest.TestCase): def write_settings(self, filename, apps=None, is_dir=False, sdict=None): - test_dir = os.path.dirname(os.path.dirname(upath(__file__))) if is_dir: settings_dir = os.path.join(test_dir, filename) os.mkdir(settings_dir) @@ -36,7 +44,7 @@ class AdminScriptTestCase(unittest.TestCase): settings_file_path = os.path.join(test_dir, filename) with open(settings_file_path, 'w') as settings_file: - settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n') + settings_file.write('# Settings file automatically generated by admin_scripts test case\n') exports = [ 'DATABASES', 'ROOT_URLCONF', @@ -50,7 +58,7 @@ class AdminScriptTestCase(unittest.TestCase): settings_file.write("%s = %s\n" % (s, o)) if apps is None: - apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'regressiontests.admin_scripts'] + apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts'] settings_file.write("INSTALLED_APPS = %s\n" % apps) @@ -89,16 +97,21 @@ class AdminScriptTestCase(unittest.TestCase): first_package_re = re.compile(r'(^[^\.]+)\.') for backend in settings.DATABASES.values(): result = first_package_re.findall(backend['ENGINE']) - if result and result != 'django': + 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) + project_dir = test_dir + base_dir = os.path.dirname(test_dir) + # The base dir for Django's tests is one level up. + tests_dir = os.path.dirname(os.path.dirname(__file__)) + # The base dir for Django is one level above the test dir. We don't use + # `import django` to figure that out, so we don't pick up a Django + # from site-packages or similar. + django_dir = os.path.dirname(tests_dir) ext_backend_base_dirs = self._ext_backend_paths() # Remember the old environment @@ -116,7 +129,7 @@ class AdminScriptTestCase(unittest.TestCase): os.environ['DJANGO_SETTINGS_MODULE'] = settings_file elif 'DJANGO_SETTINGS_MODULE' in os.environ: del os.environ['DJANGO_SETTINGS_MODULE'] - python_path = [project_dir, base_dir] + python_path = [base_dir, django_dir, tests_dir] python_path.extend(ext_backend_base_dirs) os.environ[python_path_var_name] = os.pathsep.join(python_path) @@ -125,7 +138,6 @@ class AdminScriptTestCase(unittest.TestCase): out, err = subprocess.Popen([sys.executable, script] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).communicate() - # Restore the old environment if old_django_settings_module: os.environ['DJANGO_SETTINGS_MODULE'] = old_django_settings_module @@ -156,7 +168,7 @@ class AdminScriptTestCase(unittest.TestCase): with open(test_manage_py, 'r') as fp: manage_py_contents = fp.read() manage_py_contents = manage_py_contents.replace( - "{{ project_name }}", "regressiontests") + "{{ project_name }}", "test_project") with open(test_manage_py, 'w') as fp: fp.write(manage_py_contents) self.addCleanup(safe_remove, test_manage_py) @@ -228,7 +240,7 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "default: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -236,7 +248,7 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -263,7 +275,7 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase): def test_custom_command_with_settings(self): "default: django-admin can execute user commands if settings are provided as argument" - args = ['noargs_command', '--settings=regressiontests.settings'] + args = ['noargs_command', '--settings=test_project.settings'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -271,7 +283,7 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -280,7 +292,7 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): 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']) + self.write_settings('settings.py', ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts']) def tearDown(self): self.remove_settings('settings.py') @@ -294,7 +306,7 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "fulldefault: django-admin builtin commands succeed if a settings file is provided" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -302,7 +314,7 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -329,7 +341,7 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): def test_custom_command_with_settings(self): "fulldefault: django-admin can execute user commands if settings are provided as argument" - args = ['noargs_command', '--settings=regressiontests.settings'] + args = ['noargs_command', '--settings=test_project.settings'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -337,7 +349,7 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -360,7 +372,7 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "minimal: django-admin builtin commands fail if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.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') @@ -368,7 +380,7 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') @@ -395,7 +407,7 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase): 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=regressiontests.settings'] + args = ['noargs_command', '--settings=test_project.settings'] out, err = self.run_django_admin(args) self.assertNoOutput(out) self.assertOutput(err, "Unknown command: 'noargs_command'") @@ -403,7 +415,7 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(out) self.assertOutput(err, "Unknown command: 'noargs_command'") @@ -426,7 +438,7 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "alternate: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.alternate_settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.alternate_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -434,7 +446,7 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase): 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, 'regressiontests.alternate_settings') + out, err = self.run_django_admin(args, 'test_project.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -461,7 +473,7 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase): def test_custom_command_with_settings(self): "alternate: django-admin can execute user commands if settings are provided as argument" - args = ['noargs_command', '--settings=regressiontests.alternate_settings'] + args = ['noargs_command', '--settings=test_project.alternate_settings'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -469,7 +481,7 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase): 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, 'regressiontests.alternate_settings') + out, err = self.run_django_admin(args, 'test_project.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -497,7 +509,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "alternate: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.alternate_settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.alternate_settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -505,7 +517,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): 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, 'regressiontests.alternate_settings') + out, err = self.run_django_admin(args, 'test_project.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -531,7 +543,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): def test_custom_command_with_settings(self): "alternate: django-admin can execute user commands if settings are provided as argument" - args = ['noargs_command', '--settings=regressiontests.alternate_settings'] + args = ['noargs_command', '--settings=test_project.alternate_settings'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -539,7 +551,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase): 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, 'regressiontests.alternate_settings') + out, err = self.run_django_admin(args, 'test_project.alternate_settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -560,17 +572,17 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase): "directory: startapp creates the correct directory" args = ['startapp', 'settings_test'] app_path = os.path.join(test_dir, 'settings_test') - out, err = self.run_django_admin(args, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.addCleanup(shutil.rmtree, app_path) self.assertNoOutput(err) self.assertTrue(os.path.exists(app_path)) def test_setup_environ_custom_template(self): "directory: startapp creates the correct directory with a custom template" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'app_template') + template_path = os.path.join(custom_templates_dir, 'app_template') args = ['startapp', '--template', template_path, 'custom_settings_test'] app_path = os.path.join(test_dir, 'custom_settings_test') - out, err = self.run_django_admin(args, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.addCleanup(shutil.rmtree, app_path) self.assertNoOutput(err) self.assertTrue(os.path.exists(app_path)) @@ -605,7 +617,7 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase): def test_builtin_with_settings(self): "directory: django-admin builtin commands succeed if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -613,7 +625,7 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_django_admin(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -632,7 +644,7 @@ class ManageNoSettings(AdminScriptTestCase): args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) - self.assertOutput(err, "Could not import settings 'regressiontests.settings'") + self.assertOutput(err, "Could not import settings 'test_project.settings'") def test_builtin_with_bad_settings(self): "no settings: manage.py builtin commands fail if settings file (from argument) doesn't exist" @@ -668,7 +680,7 @@ class ManageDefaultSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "default: manage.py builtin commands succeed if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -676,7 +688,7 @@ class ManageDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -703,7 +715,7 @@ class ManageDefaultSettings(AdminScriptTestCase): def test_custom_command_with_settings(self): "default: manage.py can execute user commands when settings are provided as argument" - args = ['noargs_command', '--settings=regressiontests.settings'] + args = ['noargs_command', '--settings=test_project.settings'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -711,7 +723,7 @@ class ManageDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -721,7 +733,7 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): 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']) + self.write_settings('settings.py', ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts']) def tearDown(self): self.remove_settings('settings.py') @@ -735,7 +747,7 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "fulldefault: manage.py builtin commands succeed if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -743,7 +755,7 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, 'CREATE TABLE') @@ -770,7 +782,7 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): def test_custom_command_with_settings(self): "fulldefault: manage.py can execute user commands when settings are provided as argument" - args = ['noargs_command', '--settings=regressiontests.settings'] + args = ['noargs_command', '--settings=test_project.settings'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -778,7 +790,7 @@ class ManageFullPathDefaultSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(err) self.assertOutput(out, "EXECUTE:NoArgsCommand") @@ -801,7 +813,7 @@ class ManageMinimalSettings(AdminScriptTestCase): def test_builtin_with_settings(self): "minimal: manage.py builtin commands fail if settings are provided as argument" - args = ['sqlall', '--settings=regressiontests.settings', 'admin_scripts'] + args = ['sqlall', '--settings=test_project.settings', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') @@ -809,7 +821,7 @@ class ManageMinimalSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(out) self.assertOutput(err, 'App with label admin_scripts could not be found') @@ -836,7 +848,7 @@ class ManageMinimalSettings(AdminScriptTestCase): 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=regressiontests.settings'] + args = ['noargs_command', '--settings=test_project.settings'] out, err = self.run_manage(args) self.assertNoOutput(out) self.assertOutput(err, "Unknown command: 'noargs_command'") @@ -844,7 +856,7 @@ class ManageMinimalSettings(AdminScriptTestCase): 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, 'regressiontests.settings') + out, err = self.run_manage(args, 'test_project.settings') self.assertNoOutput(out) self.assertOutput(err, "Unknown command: 'noargs_command'") @@ -863,7 +875,7 @@ class ManageAlternateSettings(AdminScriptTestCase): args = ['sqlall', 'admin_scripts'] out, err = self.run_manage(args) self.assertNoOutput(out) - self.assertOutput(err, "Could not import settings 'regressiontests.settings'") + self.assertOutput(err, "Could not import settings 'test_project.settings'") def test_builtin_with_settings(self): "alternate: manage.py builtin commands work with settings provided as argument" @@ -902,7 +914,7 @@ class ManageAlternateSettings(AdminScriptTestCase): args = ['noargs_command'] out, err = self.run_manage(args) self.assertNoOutput(out) - self.assertOutput(err, "Could not import settings 'regressiontests.settings'") + self.assertOutput(err, "Could not import settings 'test_project.settings'") def test_custom_command_with_settings(self): "alternate: manage.py can execute user commands if settings are provided as argument" @@ -1005,7 +1017,7 @@ class ManageSettingsWithImportError(AdminScriptTestCase): else: settings_file_path = os.path.join(test_dir, filename) with open(settings_file_path, 'w') as settings_file: - settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n') + settings_file.write('# Settings file automatically generated by admin_scripts test case\n') settings_file.write('# The next line will cause an import error:\nimport foo42bar\n') def test_builtin_command(self): @@ -1102,13 +1114,13 @@ class ManageTestCommand(AdminScriptTestCase): address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS') - self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner') + self.cmd.handle(verbosity=0, testrunner='admin_scripts.tests.CustomTestRunner') # Original state hasn't changed self.assertEqual('DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ, address_predefined) self.assertEqual(os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS'), old_address) - self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner', + self.cmd.handle(verbosity=0, testrunner='admin_scripts.tests.CustomTestRunner', liveserver='blah') # Variable was correctly set @@ -1279,6 +1291,43 @@ class CommandTypes(AdminScriptTestCase): 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_base_run_from_argv(self): + """ + Test run_from_argv properly terminates even with custom execute() (#19665) + Also test proper traceback display. + """ + command = BaseCommand() + def raise_command_error(*args, **kwargs): + raise CommandError("Custom error") + + old_stderr = sys.stderr + sys.stderr = err = StringIO() + try: + command.execute = lambda args: args # This will trigger TypeError + with self.assertRaises(SystemExit): + command.run_from_argv(['', '']) + err_message = err.getvalue() + # Exceptions other than CommandError automatically output the traceback + self.assertIn("Traceback", err_message) + self.assertIn("TypeError", err_message) + + command.execute = raise_command_error + err.truncate(0) + with self.assertRaises(SystemExit): + command.run_from_argv(['', '']) + err_message = err.getvalue() + self.assertNotIn("Traceback", err_message) + self.assertIn("CommandError", err_message) + + err.truncate(0) + with self.assertRaises(SystemExit): + command.run_from_argv(['', '', '--traceback']) + err_message = err.getvalue() + self.assertIn("Traceback (most recent call last)", err_message) + self.assertIn("CommandError", err_message) + finally: + sys.stderr = old_stderr + def test_noargs(self): "NoArg Commands can be executed" args = ['noargs_command'] @@ -1430,13 +1479,15 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_invalid_project_name(self): "Make sure the startproject management command validates a project name" - args = ['startproject', '7testproject'] - testproject_dir = os.path.join(test_dir, '7testproject') - self.addCleanup(shutil.rmtree, testproject_dir, True) + for bad_name in ('7testproject', '../testproject'): + args = ['startproject', bad_name] + testproject_dir = os.path.join(test_dir, bad_name) + self.addCleanup(shutil.rmtree, testproject_dir, True) - out, err = self.run_django_admin(args) - self.assertOutput(err, "Error: '7testproject' is not a valid project name. Please make sure the name begins with a letter or underscore.") - self.assertFalse(os.path.exists(testproject_dir)) + out, err = self.run_django_admin(args) + self.assertOutput(err, "Error: '%s' is not a valid project name. " + "Please make sure the name begins with a letter or underscore." % bad_name) + self.assertFalse(os.path.exists(testproject_dir)) def test_simple_project_different_directory(self): "Make sure the startproject management command creates a project in a specific directory" @@ -1456,7 +1507,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_custom_project_template(self): "Make sure the startproject management command is able to use a different project template" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + template_path = os.path.join(custom_templates_dir, 'project_template') args = ['startproject', '--template', template_path, 'customtestproject'] testproject_dir = os.path.join(test_dir, 'customtestproject') self.addCleanup(shutil.rmtree, testproject_dir, True) @@ -1468,7 +1519,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_template_dir_with_trailing_slash(self): "Ticket 17475: Template dir passed has a trailing path separator" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template' + os.sep) + template_path = os.path.join(custom_templates_dir, 'project_template' + os.sep) args = ['startproject', '--template', template_path, 'customtestproject'] testproject_dir = os.path.join(test_dir, 'customtestproject') self.addCleanup(shutil.rmtree, testproject_dir, True) @@ -1480,7 +1531,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_custom_project_template_from_tarball_by_path(self): "Make sure the startproject management command is able to use a different project template from a tarball" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template.tgz') + template_path = os.path.join(custom_templates_dir, 'project_template.tgz') args = ['startproject', '--template', template_path, 'tarballtestproject'] testproject_dir = os.path.join(test_dir, 'tarballtestproject') self.addCleanup(shutil.rmtree, testproject_dir, True) @@ -1492,7 +1543,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_custom_project_template_from_tarball_to_alternative_location(self): "Startproject can use a project template from a tarball and create it in a specified location" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template.tgz') + template_path = os.path.join(custom_templates_dir, 'project_template.tgz') args = ['startproject', '--template', template_path, 'tarballtestproject', 'altlocation'] testproject_dir = os.path.join(test_dir, 'altlocation') os.mkdir(testproject_dir) @@ -1531,7 +1582,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_file_without_extension(self): "Make sure the startproject management command is able to render custom files" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + template_path = os.path.join(custom_templates_dir, 'project_template') args = ['startproject', '--template', template_path, 'customtestproject', '-e', 'txt', '-n', 'Procfile'] testproject_dir = os.path.join(test_dir, 'customtestproject') self.addCleanup(shutil.rmtree, testproject_dir, True) @@ -1549,7 +1600,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_custom_project_template_context_variables(self): "Make sure template context variables are rendered with proper values" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + template_path = os.path.join(custom_templates_dir, 'project_template') args = ['startproject', '--template', template_path, 'another_project', 'project_dir'] testproject_dir = os.path.join(test_dir, 'project_dir') os.mkdir(testproject_dir) @@ -1567,7 +1618,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): # We're using a custom command so we need the alternate settings self.write_settings('alternate_settings.py') self.addCleanup(self.remove_settings, 'alternate_settings.py') - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + template_path = os.path.join(custom_templates_dir, 'project_template') args = ['custom_startproject', '--template', template_path, 'another_project', 'project_dir', '--extra', '<&>', '--settings=alternate_settings'] testproject_dir = os.path.join(test_dir, 'project_dir') os.mkdir(testproject_dir) @@ -1584,7 +1635,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): Make sure an exception is raised when the provided destination directory doesn't exist """ - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + template_path = os.path.join(custom_templates_dir, 'project_template') args = ['startproject', '--template', template_path, 'yet_another_project', 'project_dir2'] testproject_dir = os.path.join(test_dir, 'project_dir2') out, err = self.run_django_admin(args) @@ -1594,7 +1645,7 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): def test_custom_project_template_with_non_ascii_templates(self): "Ticket 18091: Make sure the startproject management command is able to render templates with non-ASCII content" - template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template') + template_path = os.path.join(custom_templates_dir, 'project_template') args = ['startproject', '--template', template_path, '--extension=txt', 'customtestproject'] testproject_dir = os.path.join(test_dir, 'customtestproject') self.addCleanup(shutil.rmtree, testproject_dir, True) @@ -1610,11 +1661,21 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): class DiffSettings(AdminScriptTestCase): """Tests for diffsettings management command.""" + def test_basic(self): - "Runs without error and emits settings diff." + """Runs without error and emits settings diff.""" self.write_settings('settings_to_diff.py', sdict={'FOO': '"bar"'}) self.addCleanup(self.remove_settings, 'settings_to_diff.py') args = ['diffsettings', '--settings=settings_to_diff'] out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "FOO = 'bar' ###") + + def test_all(self): + """The all option also shows settings with the default value.""" + self.write_settings('settings_to_diff.py', sdict={'STATIC_URL': 'None'}) + self.addCleanup(self.remove_settings, 'settings_to_diff.py') + args = ['diffsettings', '--settings=settings_to_diff', '--all'] + out, err = self.run_manage(args) + self.assertNoOutput(err) + self.assertOutput(out, "### STATIC_URL = None") diff --git a/tests/regressiontests/admin_scripts/urls.py b/tests/admin_scripts/urls.py index a45dc3e9a6..a45dc3e9a6 100644 --- a/tests/regressiontests/admin_scripts/urls.py +++ b/tests/admin_scripts/urls.py diff --git a/tests/modeltests/force_insert_update/__init__.py b/tests/admin_util/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/force_insert_update/__init__.py +++ b/tests/admin_util/__init__.py diff --git a/tests/regressiontests/admin_util/models.py b/tests/admin_util/models.py index 32a6cd6291..32a6cd6291 100644 --- a/tests/regressiontests/admin_util/models.py +++ b/tests/admin_util/models.py diff --git a/tests/regressiontests/admin_util/tests.py b/tests/admin_util/tests.py index e9e122a9f0..7898f200b5 100644 --- a/tests/regressiontests/admin_util/tests.py +++ b/tests/admin_util/tests.py @@ -5,8 +5,8 @@ from datetime import datetime from django.conf import settings from django.contrib import admin from django.contrib.admin import helpers -from django.contrib.admin.util import (display_for_field, label_for_field, - lookup_field, NestedObjects) +from django.contrib.admin.util import (display_for_field, flatten_fieldsets, + label_for_field, lookup_field, NestedObjects) from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE from django.contrib.sites.models import Site from django.db import models, DEFAULT_DB_ALIAS @@ -300,3 +300,21 @@ class UtilTests(unittest.TestCase): '<label for="id_text" class="required inline">&text:</label>') self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(), '<label for="id_cb" class="vCheckboxLabel required inline">&cb</label>') + + def test_flatten_fieldsets(self): + """ + Regression test for #18051 + """ + fieldsets = ( + (None, { + 'fields': ('url', 'title', ('content', 'sites')) + }), + ) + self.assertEqual(flatten_fieldsets(fieldsets), ['url', 'title', 'content', 'sites']) + + fieldsets = ( + (None, { + 'fields': ('url', 'title', ['content', 'sites']) + }), + ) + self.assertEqual(flatten_fieldsets(fieldsets), ['url', 'title', 'content', 'sites']) diff --git a/tests/modeltests/generic_relations/__init__.py b/tests/admin_validation/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/generic_relations/__init__.py +++ b/tests/admin_validation/__init__.py diff --git a/tests/regressiontests/admin_validation/models.py b/tests/admin_validation/models.py index d23849a2a8..d23849a2a8 100644 --- a/tests/regressiontests/admin_validation/models.py +++ b/tests/admin_validation/models.py diff --git a/tests/regressiontests/admin_validation/tests.py b/tests/admin_validation/tests.py index be2500a803..5b2c45f6f2 100644 --- a/tests/regressiontests/admin_validation/tests.py +++ b/tests/admin_validation/tests.py @@ -20,6 +20,18 @@ class InvalidFields(admin.ModelAdmin): form = SongForm fields = ['spam'] +class ValidFormFieldsets(admin.ModelAdmin): + def get_form(self, request, obj=None, **kwargs): + class ExtraFieldForm(SongForm): + name = forms.CharField(max_length=50) + return ExtraFieldForm + + fieldsets = ( + (None, { + 'fields': ('name',), + }), + ) + class ValidationTestCase(TestCase): def test_readonly_and_editable(self): @@ -42,6 +54,14 @@ class ValidationTestCase(TestCase): validate, InvalidFields, Song) + def test_custom_get_form_with_fieldsets(self): + """ + Ensure that the fieldsets validation is skipped when the ModelAdmin.get_form() method + is overridden. + Refs #19445. + """ + validate(ValidFormFieldsets, Song) + def test_exclude_values(self): """ Tests for basic validation of 'exclude' option values (#12689) @@ -122,7 +142,7 @@ class ValidationTestCase(TestCase): model = TwoAlbumFKAndAnE self.assertRaisesMessage(Exception, - "<class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'regressiontests.admin_validation.models.Album'>", + "<class 'admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'admin_validation.models.Album'>", validate_inline, TwoAlbumFKAndAnEInline, None, Album) diff --git a/tests/modeltests/get_latest/__init__.py b/tests/admin_views/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/get_latest/__init__.py +++ b/tests/admin_views/__init__.py diff --git a/tests/regressiontests/admin_views/admin.py b/tests/admin_views/admin.py index 1e4e9b5c75..cc7585cd2d 100644 --- a/tests/regressiontests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -27,7 +27,8 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated, - RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice) + RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice, + ShortMessage, Telegram) def callable_year(dt_value): @@ -70,6 +71,16 @@ class ChapterXtra1Admin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin): list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') list_filter = ('date', 'section') + fieldsets=( + ('Some fields', { + 'classes': ('collapse',), + 'fields': ('title', 'content') + }), + ('Some other fields', { + 'classes': ('wide',), + 'fields': ('date', 'section') + }) + ) def changelist_view(self, request): "Test that extra_context works" @@ -166,10 +177,10 @@ class PersonAdmin(admin.ModelAdmin): return super(PersonAdmin, self).get_changelist_formset(request, formset=BasePersonModelFormSet, **kwargs) - def queryset(self, request): + def get_queryset(self, request): # Order by a field that isn't in list display, to be able to test # whether ordering is preserved. - return super(PersonAdmin, self).queryset(request).order_by('age') + return super(PersonAdmin, self).get_queryset(request).order_by('age') class FooAccount(Account): @@ -272,8 +283,8 @@ class ParentAdmin(admin.ModelAdmin): class EmptyModelAdmin(admin.ModelAdmin): - def queryset(self, request): - return super(EmptyModelAdmin, self).queryset(request).filter(pk__gt=1) + def get_queryset(self, request): + return super(EmptyModelAdmin, self).get_queryset(request).filter(pk__gt=1) class OldSubscriberAdmin(admin.ModelAdmin): @@ -416,8 +427,8 @@ class PostAdmin(admin.ModelAdmin): class CustomChangeList(ChangeList): - def get_query_set(self, request): - return self.root_query_set.filter(pk=9999) # Does not exist + def get_queryset(self, request): + return self.root_queryset.filter(pk=9999) # Does not exist class GadgetAdmin(admin.ModelAdmin): @@ -439,25 +450,54 @@ class FoodDeliveryAdmin(admin.ModelAdmin): list_editable = ('driver', 'restaurant') +class CoverLetterAdmin(admin.ModelAdmin): + """ + A ModelAdmin with a custom get_queryset() method that uses defer(), to test + verbose_name display in messages shown after adding/editing CoverLetter + instances. + Note that the CoverLetter model defines a __unicode__ method. + For testing fix for ticket #14529. + """ + + def get_queryset(self, request): + return super(CoverLetterAdmin, self).get_queryset(request).defer('date_written') + + class PaperAdmin(admin.ModelAdmin): """ - A ModelAdmin with a custom queryset() method that uses only(), to test - verbose_name display in messages shown after adding Paper instances. + A ModelAdmin with a custom get_queryset() method that uses only(), to test + verbose_name display in messages shown after adding/editing Paper + instances. + For testing fix for ticket #14529. """ - def queryset(self, request): - return super(PaperAdmin, self).queryset(request).only('title') + def get_queryset(self, request): + return super(PaperAdmin, self).get_queryset(request).only('title') -class CoverLetterAdmin(admin.ModelAdmin): +class ShortMessageAdmin(admin.ModelAdmin): """ - A ModelAdmin with a custom queryset() method that uses only(), to test - verbose_name display in messages shown after adding CoverLetter instances. - Note that the CoverLetter model defines a __unicode__ method. + A ModelAdmin with a custom get_queryset() method that uses defer(), to test + verbose_name display in messages shown after adding/editing ShortMessage + instances. + For testing fix for ticket #14529. + """ + + def get_queryset(self, request): + return super(ShortMessageAdmin, self).get_queryset(request).defer('timestamp') + + +class TelegramAdmin(admin.ModelAdmin): + """ + A ModelAdmin with a custom get_queryset() method that uses only(), to test + verbose_name display in messages shown after adding/editing Telegram + instances. + Note that the Telegram model defines a __unicode__ method. + For testing fix for ticket #14529. """ - def queryset(self, request): - return super(CoverLetterAdmin, self).queryset(request).defer('date_written') + def get_queryset(self, request): + return super(TelegramAdmin, self).get_queryset(request).only('title') class StoryForm(forms.ModelForm): @@ -665,6 +705,8 @@ site.register(FoodDelivery, FoodDeliveryAdmin) site.register(RowLevelChangePermissionModel, RowLevelChangePermissionModelAdmin) site.register(Paper, PaperAdmin) site.register(CoverLetter, CoverLetterAdmin) +site.register(ShortMessage, ShortMessageAdmin) +site.register(Telegram, TelegramAdmin) site.register(Story, StoryAdmin) site.register(OtherStory, OtherStoryAdmin) site.register(Report, ReportAdmin) diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/admin_views/customadmin.py index d69d690af0..c204b81edd 100644 --- a/tests/regressiontests/admin_views/customadmin.py +++ b/tests/admin_views/customadmin.py @@ -35,8 +35,8 @@ class Admin2(admin.AdminSite): class UserLimitedAdmin(UserAdmin): # used for testing password change on a user not in queryset - def queryset(self, request): - qs = super(UserLimitedAdmin, self).queryset(request) + def get_queryset(self, request): + qs = super(UserLimitedAdmin, self).get_queryset(request) return qs.filter(is_superuser=False) diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml b/tests/admin_views/fixtures/admin-views-actions.xml index 316e750577..316e750577 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml +++ b/tests/admin_views/fixtures/admin-views-actions.xml diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-books.xml b/tests/admin_views/fixtures/admin-views-books.xml index 0517ed8d8a..0517ed8d8a 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-books.xml +++ b/tests/admin_views/fixtures/admin-views-books.xml diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-colors.xml b/tests/admin_views/fixtures/admin-views-colors.xml index e1213561b9..e1213561b9 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-colors.xml +++ b/tests/admin_views/fixtures/admin-views-colors.xml diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml b/tests/admin_views/fixtures/admin-views-fabrics.xml index 485bb27c2a..485bb27c2a 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml +++ b/tests/admin_views/fixtures/admin-views-fabrics.xml diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-person.xml b/tests/admin_views/fixtures/admin-views-person.xml index ff00fd2169..ff00fd2169 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-person.xml +++ b/tests/admin_views/fixtures/admin-views-person.xml diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml b/tests/admin_views/fixtures/admin-views-unicode.xml index 5652aa1881..5652aa1881 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-unicode.xml +++ b/tests/admin_views/fixtures/admin-views-unicode.xml diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-users.xml b/tests/admin_views/fixtures/admin-views-users.xml index 1c85e1c909..1c85e1c909 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-users.xml +++ b/tests/admin_views/fixtures/admin-views-users.xml diff --git a/tests/regressiontests/admin_views/fixtures/deleted-objects.xml b/tests/admin_views/fixtures/deleted-objects.xml index 92e43dba1c..92e43dba1c 100644 --- a/tests/regressiontests/admin_views/fixtures/deleted-objects.xml +++ b/tests/admin_views/fixtures/deleted-objects.xml diff --git a/tests/regressiontests/admin_views/fixtures/multiple-child-classes.json b/tests/admin_views/fixtures/multiple-child-classes.json index 5cadf4c1c5..5cadf4c1c5 100644 --- a/tests/regressiontests/admin_views/fixtures/multiple-child-classes.json +++ b/tests/admin_views/fixtures/multiple-child-classes.json diff --git a/tests/regressiontests/admin_views/fixtures/string-primary-key.xml b/tests/admin_views/fixtures/string-primary-key.xml index 1792cb2d7e..1792cb2d7e 100644 --- a/tests/regressiontests/admin_views/fixtures/string-primary-key.xml +++ b/tests/admin_views/fixtures/string-primary-key.xml diff --git a/tests/regressiontests/admin_views/forms.py b/tests/admin_views/forms.py index e8493df95b..e8493df95b 100644 --- a/tests/regressiontests/admin_views/forms.py +++ b/tests/admin_views/forms.py diff --git a/tests/regressiontests/admin_views/models.py b/tests/admin_views/models.py index c641cfc0e6..1916949f63 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -184,7 +184,7 @@ class Person(models.Model): name = models.CharField(max_length=100) gender = models.IntegerField(choices=GENDER_CHOICES) age = models.IntegerField(default=21) - alive = models.BooleanField() + alive = models.BooleanField(default=True) def __str__(self): return self.name @@ -196,7 +196,7 @@ 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) + name = models.CharField(blank=False, max_length=80) def __str__(self): return self.name @@ -207,7 +207,7 @@ 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) + username = models.CharField(blank=False, max_length=80) persona = models.ForeignKey(Persona, related_name="accounts") servicename = 'generic service' @@ -548,18 +548,32 @@ class FoodDelivery(models.Model): unique_together = (("driver", "restaurant"),) +@python_2_unicode_compatible +class CoverLetter(models.Model): + author = models.CharField(max_length=30) + date_written = models.DateField(null=True, blank=True) + + def __str__(self): + return self.author + + class Paper(models.Model): title = models.CharField(max_length=30) author = models.CharField(max_length=30, blank=True, null=True) +class ShortMessage(models.Model): + content = models.CharField(max_length=140) + timestamp = models.DateTimeField(null=True, blank=True) + + @python_2_unicode_compatible -class CoverLetter(models.Model): - author = models.CharField(max_length=30) - date_written = models.DateField(null=True, blank=True) +class Telegram(models.Model): + title = models.CharField(max_length=30) + date_sent = models.DateField(null=True, blank=True) def __str__(self): - return self.author + return self.title class Story(models.Model): diff --git a/tests/regressiontests/admin_views/templates/custom_filter_template.html b/tests/admin_views/templates/custom_filter_template.html index e5c9a8e7d8..e5c9a8e7d8 100644 --- a/tests/regressiontests/admin_views/templates/custom_filter_template.html +++ b/tests/admin_views/templates/custom_filter_template.html diff --git a/tests/regressiontests/admin_views/tests.py b/tests/admin_views/tests.py index 669695ea9b..e0000ff6b5 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -6,7 +6,7 @@ import re import datetime try: from urllib.parse import urljoin -except ImportError: # Python 2 +except ImportError: # Python 2 from urlparse import urljoin from django.conf import settings, global_settings @@ -25,6 +25,7 @@ from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD from django.contrib.contenttypes.models import ContentType +from django.core.urlresolvers import reverse from django.forms.util import ErrorList from django.template.response import TemplateResponse from django.test import TestCase @@ -32,7 +33,7 @@ from django.utils import formats, translation, unittest from django.utils.cache import get_max_age from django.utils.encoding import iri_to_uri, force_bytes from django.utils.html import escape -from django.utils.http import urlencode +from django.utils.http import urlencode, urlquote from django.utils._os import upath from django.utils import six from django.test.utils import override_settings @@ -47,7 +48,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount, OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject, - Simple, UndeletableObject, Choice) + Simple, UndeletableObject, Choice, ShortMessage, Telegram) ERROR_MESSAGE = "Please enter the correct username and password \ @@ -64,7 +65,7 @@ class AdminViewBasicTest(TestCase): # this test case and changing urlbit. urlbit = 'admin' - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" def setUp(self): self.old_USE_I18N = settings.USE_I18N @@ -124,13 +125,22 @@ class AdminViewBasicTest(TestCase): 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. + 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 testBasicInheritanceGetStringPK(self): + """ + Ensure GET on the change_view works on inherited models (returns an + HTTP 404 error, see #19951) 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/supervillain/abc/' % self.urlbit) + self.assertEqual(response.status_code, 404) + def testBasicAddPost(self): """ A smoke test to ensure POST on add_view works. @@ -282,7 +292,7 @@ class AdminViewBasicTest(TestCase): """ If no ordering is defined in `ModelAdmin.ordering` or in the query string, then the underlying order of the queryset should not be - changed, even if it is defined in `Modeladmin.queryset()`. + changed, even if it is defined in `Modeladmin.get_queryset()`. Refs #11868, #7309. """ p1 = Person.objects.create(name="Amy", gender=1, alive=True, age=80) @@ -431,7 +441,7 @@ class AdminViewBasicTest(TestCase): self.urlbit, query_string)) self.assertEqual(filtered_response.status_code, 200) # ensure changelist contains only valid objects - for obj in filtered_response.context['cl'].query_set.all(): + for obj in filtered_response.context['cl'].queryset.all(): self.assertTrue(params['test'](obj, value)) def testIncorrectLookupParameters(self): @@ -610,7 +620,7 @@ class AdminViewBasicTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewFormUrlTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ["admin-views-users.xml"] urlbit = "admin3" @@ -637,14 +647,14 @@ class AdminViewFormUrlTest(TestCase): os.path.join(os.path.dirname(upath(__file__)), 'templates'),) with self.settings(TEMPLATE_DIRS=template_dirs): response = self.client.get("/test_admin/admin/admin_views/color2/") - self.assertTrue('custom_filter_template.html' in [t.name for t in response.templates]) + self.assertTemplateUsed(response, 'custom_filter_template.html') @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminJavaScriptTest(TestCase): fixtures = ['admin-views-users.xml'] - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" def setUp(self): self.client.login(username='super', password='secret') @@ -709,7 +719,7 @@ class AdminJavaScriptTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class SaveAsTests(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-person.xml'] def setUp(self): @@ -739,7 +749,7 @@ class SaveAsTests(TestCase): class CustomModelAdminTest(AdminViewBasicTest): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" urlbit = "admin2" def testCustomAdminSiteLoginForm(self): @@ -816,7 +826,7 @@ def get_perm(Model, perm): class AdminViewPermissionsTest(TestCase): """Tests for Admin Views Permissions.""" - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -972,6 +982,32 @@ class AdminViewPermissionsTest(TestCase): login = self.client.post('/test_admin/admin/', dict(self.super_login, **new_next), QUERY_STRING=query_string) self.assertRedirects(login, redirect_url) + def testDoubleLoginIsNotAllowed(self): + """Regression test for #19327""" + response = self.client.get('/test_admin/admin/') + self.assertEqual(response.status_code, 200) + + # Establish a valid admin session + login = self.client.post('/test_admin/admin/', self.super_login) + self.assertRedirects(login, '/test_admin/admin/') + self.assertFalse(login.context) + + # Logging in with non-admin user fails + login = self.client.post('/test_admin/admin/', self.joepublic_login) + self.assertEqual(login.status_code, 200) + self.assertContains(login, ERROR_MESSAGE) + + # Establish a valid admin session + login = self.client.post('/test_admin/admin/', self.super_login) + self.assertRedirects(login, '/test_admin/admin/') + self.assertFalse(login.context) + + # Logging in with admin user while already logged in + 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/') + def testAddView(self): """Test add view restricts access and actually adds items.""" @@ -1103,6 +1139,46 @@ class AdminViewPermissionsTest(TestCase): self.assertContains(response, 'login-form') self.client.get('/test_admin/admin/logout/') + def testHistoryView(self): + """History view should restrict access.""" + + # 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) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.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) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 200) + + # Test redirection when using row-level change permissions. Refs #11513. + RowLevelChangePermissionModel.objects.create(id=1, name="odd id") + RowLevelChangePermissionModel.objects.create(id=2, name="even id") + for login_dict in [self.super_login, self.changeuser_login, self.adduser_login, self.deleteuser_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 403) + + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + + self.client.get('/test_admin/admin/logout/') + + for login_dict in [self.joepublic_login, self.no_username_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + + self.client.get('/test_admin/admin/logout/') + def testConditionallyShowAddSectionLink(self): """ The foreign key widget should only show the "add related" button if the @@ -1217,7 +1293,7 @@ class AdminViewPermissionsTest(TestCase): class AdminViewsNoUrlTest(TestCase): """Regression test for #17333""" - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -1247,7 +1323,7 @@ class AdminViewsNoUrlTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewDeletedObjectsTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'deleted-objects.xml'] def setUp(self): @@ -1263,7 +1339,7 @@ class AdminViewDeletedObjectsTest(TestCase): """ pattern = re.compile(br"""<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.assertRegexpMatches(response.content, pattern) + six.assertRegex(self, response.content, pattern) def test_cyclic(self): """ @@ -1365,7 +1441,7 @@ class AdminViewDeletedObjectsTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewStringPrimaryKeyTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'string-primary-key.xml'] def __init__(self, *args): @@ -1401,21 +1477,23 @@ class AdminViewStringPrimaryKeyTest(TestCase): "Link to the changeform of the object in changelist should use reverse() and be quoted -- #18072" prefix = '/test_admin/admin/admin_views/modelwithstringprimarykey/' response = self.client.get(prefix) - # this URL now comes through reverse(), thus iri_to_uri encoding - pk_final_url = escape(iri_to_uri(quote(self.pk))) + # this URL now comes through reverse(), thus url quoting and iri_to_uri encoding + pk_final_url = escape(iri_to_uri(urlquote(quote(self.pk)))) should_contain = """<th><a href="%s%s/">%s</a></th>""" % (prefix, pk_final_url, 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>""" % (escape(quote(self.pk)), escape(self.pk)) + link = reverse('admin:admin_views_modelwithstringprimarykey_change', args=(quote(self.pk),)) + should_contain = """<a href="%s">%s</a>""" % (link, 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>""" % (escape(quote(self.pk)), escape(self.pk)) + link = reverse('admin:admin_views_modelwithstringprimarykey_change', args=(quote(self.pk),)) + should_contain = """<a href="%s">%s</a>""" % (link, escape(self.pk)) self.assertContains(response, should_contain) should_contain = "Model with string primary key" # capitalized in Recent Actions self.assertContains(response, should_contain) @@ -1435,8 +1513,8 @@ class AdminViewStringPrimaryKeyTest(TestCase): 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>""" % (escape(iri_to_uri(quote(self.pk))), escape(self.pk)) + # this URL now comes through reverse(), thus url quoting and iri_to_uri encoding + should_contain = """/%s/">%s</a>""" % (escape(iri_to_uri(urlquote(quote(self.pk)))), escape(self.pk)) self.assertContains(response, should_contain) def test_url_conflicts_with_add(self): @@ -1486,7 +1564,7 @@ class AdminViewStringPrimaryKeyTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class SecureViewTests(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -1641,12 +1719,12 @@ class SecureViewTests(TestCase): response = self.client.get(shortcut_url, follow=False) # Can't use self.assertRedirects() because User.get_absolute_url() is silly. self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], 'http://example.com/users/super/') + self.assertEqual(response.url, 'http://example.com/users/super/') @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewUnicodeTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-unicode.xml'] def setUp(self): @@ -1701,7 +1779,7 @@ class AdminViewUnicodeTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewListEditable(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-person.xml'] def setUp(self): @@ -2078,7 +2156,7 @@ class AdminViewListEditable(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminSearchTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users', 'multiple-child-classes', 'admin-views-person'] @@ -2126,7 +2204,7 @@ class AdminSearchTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminInheritedInlinesTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -2214,7 +2292,7 @@ class AdminInheritedInlinesTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminActionsTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-actions.xml'] def setUp(self): @@ -2433,7 +2511,7 @@ class AdminActionsTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class TestCustomChangeList(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] urlbit = 'admin' @@ -2454,7 +2532,7 @@ class TestCustomChangeList(TestCase): 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 + # Ensure 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') @@ -2462,7 +2540,7 @@ class TestCustomChangeList(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class TestInlineNotEditable(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -2482,7 +2560,7 @@ class TestInlineNotEditable(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminCustomQuerysetTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -2497,6 +2575,34 @@ class AdminCustomQuerysetTest(TestCase): else: self.assertNotContains(response, 'Primary key = %s' % i) + def test_changelist_view_count_queries(self): + # create 2 Person objects + Person.objects.create(name='person1', gender=1) + Person.objects.create(name='person2', gender=2) + + # 4 queries are expected: 1 for the session, 1 for the user, + # 1 for the count and 1 for the objects on the page + with self.assertNumQueries(4): + resp = self.client.get('/test_admin/admin/admin_views/person/') + self.assertEqual(resp.context['selection_note'], '0 of 2 selected') + self.assertEqual(resp.context['selection_note_all'], 'All 2 selected') + with self.assertNumQueries(4): + extra = {'q': 'not_in_name'} + resp = self.client.get('/test_admin/admin/admin_views/person/', extra) + self.assertEqual(resp.context['selection_note'], '0 of 0 selected') + self.assertEqual(resp.context['selection_note_all'], 'All 0 selected') + with self.assertNumQueries(4): + extra = {'q': 'person'} + resp = self.client.get('/test_admin/admin/admin_views/person/', extra) + self.assertEqual(resp.context['selection_note'], '0 of 2 selected') + self.assertEqual(resp.context['selection_note_all'], 'All 2 selected') + # here one more count(*) query will run, because filters were applied + with self.assertNumQueries(5): + extra = {'gender__exact': '1'} + resp = self.client.get('/test_admin/admin/admin_views/person/', extra) + self.assertEqual(resp.context['selection_note'], '0 of 1 selected') + self.assertEqual(resp.context['selection_note_all'], '1 selected') + def test_change_view(self): for i in self.pks: response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i) @@ -2505,27 +2611,93 @@ class AdminCustomQuerysetTest(TestCase): else: self.assertEqual(response.status_code, 404) + def test_add_model_modeladmin_defer_qs(self): + # Test for #14529. defer() is used in ModelAdmin.get_queryset() + + # model has __unicode__ method + self.assertEqual(CoverLetter.objects.count(), 0) + # Emulate model instance creation via the admin + post_data = { + "author": "Candidate, Best", + "_save": "Save", + } + response = self.client.post('/test_admin/admin/admin_views/coverletter/add/', + post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(CoverLetter.objects.count(), 1) + # Message should contain non-ugly model verbose name + self.assertContains( + response, + '<li class="success">The cover letter "Candidate, Best" was added successfully.</li>', + html=True + ) + + # model has no __unicode__ method + self.assertEqual(ShortMessage.objects.count(), 0) + # Emulate model instance creation via the admin + post_data = { + "content": "What's this SMS thing?", + "_save": "Save", + } + response = self.client.post('/test_admin/admin/admin_views/shortmessage/add/', + post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(ShortMessage.objects.count(), 1) + # Message should contain non-ugly model verbose name + self.assertContains( + response, + '<li class="success">The short message "ShortMessage object" was added successfully.</li>', + html=True + ) + def test_add_model_modeladmin_only_qs(self): - # only() is used in ModelAdmin.queryset() - p = Paper.objects.create(title="My Paper Title") - self.assertEqual(Paper.objects.count(), 1) - response = self.client.get('/test_admin/admin/admin_views/paper/%s/' % p.pk) + # Test for #14529. only() is used in ModelAdmin.get_queryset() + + # model has __unicode__ method + self.assertEqual(Telegram.objects.count(), 0) + # Emulate model instance creation via the admin + post_data = { + "title": "Urgent telegram", + "_save": "Save", + } + response = self.client.post('/test_admin/admin/admin_views/telegram/add/', + post_data, follow=True) self.assertEqual(response.status_code, 200) + self.assertEqual(Telegram.objects.count(), 1) + # Message should contain non-ugly model verbose name + self.assertContains( + response, + '<li class="success">The telegram "Urgent telegram" was added successfully.</li>', + html=True + ) + + # model has no __unicode__ method + self.assertEqual(Paper.objects.count(), 0) + # Emulate model instance creation via the admin post_data = { "title": "My Modified Paper Title", "_save": "Save", } - response = self.client.post('/test_admin/admin/admin_views/paper/%s/' % p.pk, + response = self.client.post('/test_admin/admin/admin_views/paper/add/', post_data, follow=True) self.assertEqual(response.status_code, 200) - # Message should contain non-ugly model name. Instance representation is set by six.text_type() (ugly) - self.assertContains(response, '<li class="info">The paper "Paper_Deferred_author object" was changed successfully.</li>', html=True) + self.assertEqual(Paper.objects.count(), 1) + # Message should contain non-ugly model verbose name + self.assertContains( + response, + '<li class="success">The paper "Paper object" was added successfully.</li>', + html=True + ) + + def test_edit_model_modeladmin_defer_qs(self): + # Test for #14529. defer() is used in ModelAdmin.get_queryset() - # defer() is used in ModelAdmin.queryset() + # model has __unicode__ method cl = CoverLetter.objects.create(author="John Doe") self.assertEqual(CoverLetter.objects.count(), 1) response = self.client.get('/test_admin/admin/admin_views/coverletter/%s/' % cl.pk) self.assertEqual(response.status_code, 200) + # Emulate model instance edit via the admin post_data = { "author": "John Doe II", "_save": "Save", @@ -2533,13 +2705,88 @@ class AdminCustomQuerysetTest(TestCase): response = self.client.post('/test_admin/admin/admin_views/coverletter/%s/' % cl.pk, post_data, follow=True) self.assertEqual(response.status_code, 200) - # Message should contain non-ugly model name. Instance representation is set by model's __unicode__() - self.assertContains(response, '<li class="info">The cover letter "John Doe II" was changed successfully.</li>', html=True) + self.assertEqual(CoverLetter.objects.count(), 1) + # Message should contain non-ugly model verbose name. Instance + # representation is set by model's __unicode__() + self.assertContains( + response, + '<li class="success">The cover letter "John Doe II" was changed successfully.</li>', + html=True + ) + + # model has no __unicode__ method + sm = ShortMessage.objects.create(content="This is expensive") + self.assertEqual(ShortMessage.objects.count(), 1) + response = self.client.get('/test_admin/admin/admin_views/shortmessage/%s/' % sm.pk) + self.assertEqual(response.status_code, 200) + # Emulate model instance edit via the admin + post_data = { + "content": "Too expensive", + "_save": "Save", + } + response = self.client.post('/test_admin/admin/admin_views/shortmessage/%s/' % sm.pk, + post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(ShortMessage.objects.count(), 1) + # Message should contain non-ugly model verbose name. The ugly(!) + # instance representation is set by six.text_type() + self.assertContains( + response, + '<li class="success">The short message "ShortMessage_Deferred_timestamp object" was changed successfully.</li>', + html=True + ) + + def test_edit_model_modeladmin_only_qs(self): + # Test for #14529. only() is used in ModelAdmin.get_queryset() + + # model has __unicode__ method + t = Telegram.objects.create(title="Frist Telegram") + self.assertEqual(Telegram.objects.count(), 1) + response = self.client.get('/test_admin/admin/admin_views/telegram/%s/' % t.pk) + self.assertEqual(response.status_code, 200) + # Emulate model instance edit via the admin + post_data = { + "title": "Telegram without typo", + "_save": "Save", + } + response = self.client.post('/test_admin/admin/admin_views/telegram/%s/' % t.pk, + post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(Telegram.objects.count(), 1) + # Message should contain non-ugly model verbose name. The instance + # representation is set by model's __unicode__() + self.assertContains( + response, + '<li class="success">The telegram "Telegram without typo" was changed successfully.</li>', + html=True + ) + + # model has no __unicode__ method + p = Paper.objects.create(title="My Paper Title") + self.assertEqual(Paper.objects.count(), 1) + response = self.client.get('/test_admin/admin/admin_views/paper/%s/' % p.pk) + self.assertEqual(response.status_code, 200) + # Emulate model instance edit via the admin + post_data = { + "title": "My Modified Paper Title", + "_save": "Save", + } + response = self.client.post('/test_admin/admin/admin_views/paper/%s/' % p.pk, + post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(Paper.objects.count(), 1) + # Message should contain non-ugly model verbose name. The ugly(!) + # instance representation is set by six.text_type() + self.assertContains( + response, + '<li class="success">The paper "Paper_Deferred_author object" was changed successfully.</li>', + html=True + ) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminInlineFileUploadTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-actions.xml'] urlbit = 'admin' @@ -2586,7 +2833,7 @@ class AdminInlineFileUploadTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminInlineTests(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -2905,7 +3152,7 @@ class AdminInlineTests(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class NeverCacheTests(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml'] def setUp(self): @@ -2979,7 +3226,7 @@ class NeverCacheTests(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class PrePopulatedTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3014,12 +3261,12 @@ class PrePopulatedTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) -class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): +class SeleniumAdminViewsFirefoxTests(AdminSeleniumWebDriverTestCase): webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] - def test_basic(self): + def test_prepopulated_fields(self): """ Ensure that the JavaScript-automated prepopulated fields work with the main form and with stacked and tabular inlines. @@ -3081,16 +3328,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): # Save and check that everything is properly stored in the database self.selenium.find_element_by_xpath('//input[@value="Save"]').click() - - try: - # Wait for the next page to be loaded. - self.wait_loaded_tag('body') - except TimeoutException: - # IE7 occasionnally returns an error "Internet Explorer cannot - # display the webpage" and doesn't load the next page. We just - # ignore it. - pass - + self.wait_page_loaded() self.assertEqual(MainPrepopulated.objects.all().count(), 1) MainPrepopulated.objects.get( name=' this is the mAin nÀMë and it\'s awεšome', @@ -3129,18 +3367,34 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase): slug2='option-one-tabular-inline-ignored-characters', ) + def test_collapsible_fieldset(self): + """ + Test that the 'collapse' class in fieldsets definition allows to + show/hide the appropriate field section. + """ + self.admin_login(username='super', password='secret', login_url='/test_admin/admin/') + self.selenium.get('%s%s' % (self.live_server_url, + '/test_admin/admin/admin_views/article/add/')) + self.assertFalse(self.selenium.find_element_by_id('id_title').is_displayed()) + self.selenium.find_elements_by_link_text('Show')[0].click() + self.assertTrue(self.selenium.find_element_by_id('id_title').is_displayed()) + self.assertEqual( + self.selenium.find_element_by_id('fieldsetcollapser0').text, + "Hide" + ) -class SeleniumPrePopulatedChromeTests(SeleniumPrePopulatedFirefoxTests): + +class SeleniumAdminViewsChromeTests(SeleniumAdminViewsFirefoxTests): webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver' -class SeleniumPrePopulatedIETests(SeleniumPrePopulatedFirefoxTests): +class SeleniumAdminViewsIETests(SeleniumAdminViewsFirefoxTests): webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver' @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class ReadonlyTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3228,7 +3482,7 @@ class ReadonlyTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class RawIdFieldsTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3266,7 +3520,7 @@ class UserAdminTest(TestCase): """ Tests user CRUD functionality. """ - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3351,7 +3605,7 @@ class UserAdminTest(TestCase): # Don't depend on a warm cache, see #17377. ContentType.objects.clear_cache() - with self.assertNumQueries(8): + with self.assertNumQueries(10): response = self.client.get('/test_admin/admin/auth/user/%s/' % u.pk) self.assertEqual(response.status_code, 200) @@ -3367,7 +3621,7 @@ class GroupAdminTest(TestCase): """ Tests group CRUD functionality. """ - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3389,14 +3643,14 @@ class GroupAdminTest(TestCase): def test_group_permission_performance(self): g = Group.objects.create(name="test_group") - with self.assertNumQueries(6): # instead of 259! + with self.assertNumQueries(8): # instead of 259! response = self.client.get('/test_admin/admin/auth/group/%s/' % g.pk) self.assertEqual(response.status_code, 200) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class CSSTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3452,7 +3706,7 @@ except ImportError: @unittest.skipUnless(docutils, "no docutils installed.") @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminDocsTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3495,7 +3749,7 @@ class AdminDocsTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class ValidXHTMLTests(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] urlbit = 'admin' @@ -3519,7 +3773,7 @@ class ValidXHTMLTests(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class DateHierarchyTests(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3545,14 +3799,14 @@ class DateHierarchyTests(TestCase): def assert_contains_month_link(self, response, date): self.assertContains( - response, '?release_date__year=%d&release_date__month=%d"' % ( - date.year, date.month)) + response, '?release_date__month=%d&release_date__year=%d"' % ( + date.month, date.year)) def assert_contains_day_link(self, response, date): self.assertContains( - response, '?release_date__year=%d&' - 'release_date__month=%d&release_date__day=%d"' % ( - date.year, date.month, date.day)) + response, '?release_date__day=%d&' + 'release_date__month=%d&release_date__year=%d"' % ( + date.day, date.month, date.year)) def test_empty(self): """ @@ -3651,7 +3905,7 @@ class AdminCustomSaveRelatedTests(TestCase): Ensure that one can easily customize the way related objects are saved. Refs #16115. """ - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3716,7 +3970,7 @@ class AdminCustomSaveRelatedTests(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminViewLogoutTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -3728,7 +3982,7 @@ class AdminViewLogoutTest(TestCase): def test_client_logout_url_can_be_used_to_login(self): response = self.client.get('/test_admin/admin/logout/') self.assertEqual(response.status_code, 200) - self.assertEqual(response.template_name, 'registration/logged_out.html') + self.assertTemplateUsed(response, 'registration/logged_out.html') self.assertEqual(response.request['PATH_INFO'], '/test_admin/admin/logout/') # we are now logged out @@ -3738,14 +3992,14 @@ class AdminViewLogoutTest(TestCase): # follow the redirect and test results. response = self.client.get('/test_admin/admin/logout/', follow=True) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template_name, 'admin/login.html') + self.assertTemplateUsed(response, 'admin/login.html') self.assertEqual(response.request['PATH_INFO'], '/test_admin/admin/') self.assertContains(response, '<input type="hidden" name="next" value="/test_admin/admin/" />') @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminUserMessageTest(TestCase): - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): diff --git a/tests/regressiontests/admin_views/urls.py b/tests/admin_views/urls.py index 441834cd2a..441834cd2a 100644 --- a/tests/regressiontests/admin_views/urls.py +++ b/tests/admin_views/urls.py diff --git a/tests/regressiontests/admin_views/views.py b/tests/admin_views/views.py index bb5f24ebfe..bb5f24ebfe 100644 --- a/tests/regressiontests/admin_views/views.py +++ b/tests/admin_views/views.py diff --git a/tests/modeltests/get_object_or_404/__init__.py b/tests/admin_widgets/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/get_object_or_404/__init__.py +++ b/tests/admin_widgets/__init__.py diff --git a/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml b/tests/admin_widgets/fixtures/admin-widgets-users.xml index b851562a1c..b851562a1c 100644 --- a/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml +++ b/tests/admin_widgets/fixtures/admin-widgets-users.xml diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/admin_widgets/models.py index 2977b86f3e..ae19d58cc4 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/admin_widgets/models.py @@ -37,8 +37,8 @@ class Album(models.Model): return self.name class HiddenInventoryManager(models.Manager): - def get_query_set(self): - return super(HiddenInventoryManager, self).get_query_set().filter(hidden=False) + def get_queryset(self): + return super(HiddenInventoryManager, self).get_queryset().filter(hidden=False) @python_2_unicode_compatible class Inventory(models.Model): diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 76aa9f448f..4e09922893 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -425,13 +425,6 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase): '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_STATIC_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_static_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, ['1']), True) - self.assertEqual(w._has_changed([1, 2], ['1', '2']), False) - self.assertEqual(w._has_changed([1, 2], ['1']), True) - self.assertEqual(w._has_changed([1, 2], ['1', '3']), True) - def test_m2m_related_model_not_in_admin(self): # M2M relationship with model not registered with admin site. Raw ID # widget should have no magnifying glass link. See #16542 @@ -467,7 +460,7 @@ class RelatedFieldWidgetWrapperTests(DjangoTestCase): class DateTimePickerSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' fixtures = ['admin-widgets-users.xml'] - urls = "regressiontests.admin_widgets.urls" + urls = "admin_widgets.urls" def test_show_hide_date_time_picker_widgets(self): """ @@ -523,7 +516,7 @@ class DateTimePickerSeleniumIETests(DateTimePickerSeleniumFirefoxTests): class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' fixtures = ['admin-widgets-users.xml'] - urls = "regressiontests.admin_widgets.urls" + urls = "admin_widgets.urls" def setUp(self): self.lisa = models.Student.objects.create(name='Lisa') @@ -651,12 +644,14 @@ class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCas self.selenium.get( '%s%s' % (self.live_server_url, '/admin_widgets/school/%s/' % self.school.id)) + self.wait_page_loaded() self.execute_basic_operations('vertical', 'students') self.execute_basic_operations('horizontal', 'alumni') # Save and check that everything is properly stored in the database --- self.selenium.find_element_by_xpath('//input[@value="Save"]').click() - self.school = models.School.objects.get(id=self.school.id) # Reload from database + self.wait_page_loaded() + self.school = models.School.objects.get(id=self.school.id) # Reload from database self.assertEqual(list(self.school.students.all()), [self.arthur, self.cliff, self.jason, self.john]) self.assertEqual(list(self.school.alumni.all()), @@ -735,6 +730,7 @@ class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCas # Save and check that everything is properly stored in the database --- self.selenium.find_element_by_xpath('//input[@value="Save"]').click() + self.wait_page_loaded() self.school = models.School.objects.get(id=self.school.id) # Reload from database self.assertEqual(list(self.school.students.all()), [self.jason, self.peter]) diff --git a/tests/regressiontests/admin_widgets/urls.py b/tests/admin_widgets/urls.py index aecee90b7f..aecee90b7f 100644 --- a/tests/regressiontests/admin_widgets/urls.py +++ b/tests/admin_widgets/urls.py diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/admin_widgets/widgetadmin.py index 1cdeeb9f67..1cdeeb9f67 100644 --- a/tests/regressiontests/admin_widgets/widgetadmin.py +++ b/tests/admin_widgets/widgetadmin.py diff --git a/tests/modeltests/get_or_create/__init__.py b/tests/aggregation/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/get_or_create/__init__.py +++ b/tests/aggregation/__init__.py diff --git a/tests/modeltests/aggregation/fixtures/aggregation.json b/tests/aggregation/fixtures/aggregation.json index a0021001e7..a0021001e7 100644 --- a/tests/modeltests/aggregation/fixtures/aggregation.json +++ b/tests/aggregation/fixtures/aggregation.json diff --git a/tests/modeltests/aggregation/models.py b/tests/aggregation/models.py index b4f797ee03..b4f797ee03 100644 --- a/tests/modeltests/aggregation/models.py +++ b/tests/aggregation/models.py diff --git a/tests/modeltests/aggregation/tests.py b/tests/aggregation/tests.py index c23b32fc85..c635e6ebb6 100644 --- a/tests/modeltests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -579,9 +579,9 @@ class BaseAggregateTestCase(TestCase): dates = Book.objects.annotate(num_authors=Count("authors")).dates('pubdate', 'year') self.assertQuerysetEqual( dates, [ - "datetime.datetime(1991, 1, 1, 0, 0)", - "datetime.datetime(1995, 1, 1, 0, 0)", - "datetime.datetime(2007, 1, 1, 0, 0)", - "datetime.datetime(2008, 1, 1, 0, 0)" + "datetime.date(1991, 1, 1)", + "datetime.date(1995, 1, 1)", + "datetime.date(2007, 1, 1)", + "datetime.date(2008, 1, 1)" ] ) diff --git a/tests/modeltests/invalid_models/invalid_models/__init__.py b/tests/aggregation_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/invalid_models/invalid_models/__init__.py +++ b/tests/aggregation_regress/__init__.py diff --git a/tests/regressiontests/aggregation_regress/fixtures/aggregation_regress.json b/tests/aggregation_regress/fixtures/aggregation_regress.json index 597ac041f7..597ac041f7 100644 --- a/tests/regressiontests/aggregation_regress/fixtures/aggregation_regress.json +++ b/tests/aggregation_regress/fixtures/aggregation_regress.json diff --git a/tests/regressiontests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index dd4ff50aec..047c871c39 100644 --- a/tests/regressiontests/aggregation_regress/models.py +++ b/tests/aggregation_regress/models.py @@ -1,4 +1,6 @@ # coding: utf-8 +from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -22,6 +24,13 @@ class Publisher(models.Model): return self.name +class ItemTag(models.Model): + tag = models.CharField(max_length=100) + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + + @python_2_unicode_compatible class Book(models.Model): isbn = models.CharField(max_length=9) @@ -33,6 +42,7 @@ class Book(models.Model): contact = models.ForeignKey(Author, related_name='book_contact_set') publisher = models.ForeignKey(Publisher) pubdate = models.DateField() + tags = generic.GenericRelation(ItemTag) class Meta: ordering = ('name',) @@ -63,6 +73,14 @@ class Clues(models.Model): Clue = models.CharField(max_length=150) +class WithManualPK(models.Model): + # The generic relations regression test needs two different model + # classes with the same PK value, and there are some (external) + # DB backends that don't work nicely when assigning integer to AutoField + # column (MSSQL at least). + id = models.IntegerField(primary_key=True) + + @python_2_unicode_compatible class HardbackBook(Book): weight = models.FloatField() diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py index 596ebbfaec..6c5a43664a 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/aggregation_regress/tests.py @@ -6,11 +6,14 @@ from decimal import Decimal from operator import attrgetter from django.core.exceptions import FieldError +from django.contrib.contenttypes.models import ContentType from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q from django.test import TestCase, Approximate, skipUnlessDBFeature from django.utils import six +from django.utils.unittest import expectedFailure -from .models import Author, Book, Publisher, Clues, Entries, HardbackBook +from .models import (Author, Book, Publisher, Clues, Entries, HardbackBook, + ItemTag, WithManualPK) class AggregationTests(TestCase): @@ -470,7 +473,7 @@ class AggregationTests(TestCase): # Regression for #15709 - Ensure each group_by field only exists once # per query qs = Book.objects.values('publisher').annotate(max_pages=Max('pages')).order_by() - grouping, gb_params = qs.query.get_compiler(qs.db).get_grouping([]) + grouping, gb_params = qs.query.get_compiler(qs.db).get_grouping([], []) self.assertEqual(len(grouping), 1) def test_duplicate_alias(self): @@ -546,8 +549,8 @@ class AggregationTests(TestCase): 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) + datetime.date(1995, 1, 15), + datetime.date(2007, 12, 6), ], lambda b: b ) @@ -845,14 +848,14 @@ class AggregationTests(TestCase): # The name of the explicitly provided annotation name in this case # poses no problem - qs = Author.objects.annotate(book_cnt=Count('book')).filter(book_cnt=2) + qs = Author.objects.annotate(book_cnt=Count('book')).filter(book_cnt=2).order_by('name') self.assertQuerysetEqual( qs, ['Peter Norvig'], lambda b: b.name ) # Neither in this case - qs = Author.objects.annotate(book_count=Count('book')).filter(book_count=2) + qs = Author.objects.annotate(book_count=Count('book')).filter(book_count=2).order_by('name') self.assertQuerysetEqual( qs, ['Peter Norvig'], @@ -860,7 +863,7 @@ class AggregationTests(TestCase): ) # This case used to fail because the ORM couldn't resolve the # automatically generated annotation name `book__count` - qs = Author.objects.annotate(Count('book')).filter(book__count=2) + qs = Author.objects.annotate(Count('book')).filter(book__count=2).order_by('name') self.assertQuerysetEqual( qs, ['Peter Norvig'], @@ -898,7 +901,7 @@ class AggregationTests(TestCase): # There should only be one GROUP BY clause, for the `id` column. # `name` and `age` should not be grouped on. - grouping, gb_params = results.query.get_compiler(using='default').get_grouping([]) + grouping, gb_params = results.query.get_compiler(using='default').get_grouping([], []) self.assertEqual(len(grouping), 1) assert 'id' in grouping[0] assert 'name' not in grouping[0] @@ -927,7 +930,7 @@ class AggregationTests(TestCase): def test_aggregate_duplicate_columns_only(self): # Works with only() too. results = Author.objects.only('id', 'name').annotate(num_contacts=Count('book_contact_set')) - grouping, gb_params = results.query.get_compiler(using='default').get_grouping([]) + grouping, gb_params = results.query.get_compiler(using='default').get_grouping([], []) self.assertEqual(len(grouping), 1) assert 'id' in grouping[0] assert 'name' not in grouping[0] @@ -957,7 +960,7 @@ class AggregationTests(TestCase): # And select_related() results = Book.objects.select_related('contact').annotate( num_authors=Count('authors')) - grouping, gb_params = results.query.get_compiler(using='default').get_grouping([]) + grouping, gb_params = results.query.get_compiler(using='default').get_grouping([], []) self.assertEqual(len(grouping), 1) assert 'id' in grouping[0] assert 'name' not in grouping[0] @@ -982,3 +985,119 @@ class AggregationTests(TestCase): def test_reverse_join_trimming(self): qs = Author.objects.annotate(Count('book_contact_set__contact')) self.assertIn(' JOIN ', str(qs.query)) + + def test_aggregation_with_generic_reverse_relation(self): + """ + Regression test for #10870: Aggregates with joins ignore extra + filters provided by setup_joins + + tests aggregations with generic reverse relations + """ + b = Book.objects.get(name='Practical Django Projects') + ItemTag.objects.create(object_id=b.id, tag='intermediate', + content_type=ContentType.objects.get_for_model(b)) + ItemTag.objects.create(object_id=b.id, tag='django', + content_type=ContentType.objects.get_for_model(b)) + # Assign a tag to model with same PK as the book above. If the JOIN + # used in aggregation doesn't have content type as part of the + # condition the annotation will also count the 'hi mom' tag for b. + wmpk = WithManualPK.objects.create(id=b.pk) + ItemTag.objects.create(object_id=wmpk.id, tag='hi mom', + content_type=ContentType.objects.get_for_model(wmpk)) + b = Book.objects.get(name__startswith='Paradigms of Artificial Intelligence') + ItemTag.objects.create(object_id=b.id, tag='intermediate', + content_type=ContentType.objects.get_for_model(b)) + + self.assertEqual(Book.objects.aggregate(Count('tags')), {'tags__count': 3}) + results = Book.objects.annotate(Count('tags')).order_by('-tags__count', 'name') + self.assertEqual( + [(b.name, b.tags__count) for b in results], + [ + ('Practical Django Projects', 2), + ('Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 1), + ('Artificial Intelligence: A Modern Approach', 0), + ('Python Web Development with Django', 0), + ('Sams Teach Yourself Django in 24 Hours', 0), + ('The Definitive Guide to Django: Web Development Done Right', 0) + ] + ) + + def test_negated_aggregation(self): + expected_results = Author.objects.exclude( + pk__in=Author.objects.annotate(book_cnt=Count('book')).filter(book_cnt=2) + ).order_by('name') + expected_results = [a.name for a in expected_results] + qs = Author.objects.annotate(book_cnt=Count('book')).exclude( + Q(book_cnt=2), Q(book_cnt=2)).order_by('name') + self.assertQuerysetEqual( + qs, + expected_results, + lambda b: b.name + ) + expected_results = Author.objects.exclude( + pk__in=Author.objects.annotate(book_cnt=Count('book')).filter(book_cnt=2) + ).order_by('name') + expected_results = [a.name for a in expected_results] + qs = Author.objects.annotate(book_cnt=Count('book')).exclude(Q(book_cnt=2)|Q(book_cnt=2)).order_by('name') + self.assertQuerysetEqual( + qs, + expected_results, + lambda b: b.name + ) + + def test_name_filters(self): + qs = Author.objects.annotate(Count('book')).filter( + Q(book__count__exact=2)|Q(name='Adrian Holovaty') + ).order_by('name') + self.assertQuerysetEqual( + qs, + ['Adrian Holovaty', 'Peter Norvig'], + lambda b: b.name + ) + + def test_name_expressions(self): + # Test that aggregates are spotted corretly from F objects. + # Note that Adrian's age is 34 in the fixtures, and he has one book + # so both conditions match one author. + qs = Author.objects.annotate(Count('book')).filter( + Q(name='Peter Norvig')|Q(age=F('book__count') + 33) + ).order_by('name') + self.assertQuerysetEqual( + qs, + ['Adrian Holovaty', 'Peter Norvig'], + lambda b: b.name + ) + + def test_ticket_11293(self): + q1 = Q(price__gt=50) + q2 = Q(authors__count__gt=1) + query = Book.objects.annotate(Count('authors')).filter( + q1 | q2).order_by('pk') + self.assertQuerysetEqual( + query, [1, 4, 5, 6], + lambda b: b.pk) + + def test_ticket_11293_q_immutable(self): + """ + Check that splitting a q object to parts for where/having doesn't alter + the original q-object. + """ + q1 = Q(isbn='') + q2 = Q(authors__count__gt=1) + query = Book.objects.annotate(Count('authors')) + query.filter(q1 | q2) + self.assertEqual(len(q2.children), 1) + + def test_fobj_group_by(self): + """ + Check that an F() object referring to related column works correctly + in group by. + """ + qs = Book.objects.annotate( + acount=Count('authors') + ).filter( + acount=F('publisher__num_awards') + ) + self.assertQuerysetEqual( + qs, ['Sams Teach Yourself Django in 24 Hours'], + lambda b: b.name) diff --git a/tests/modeltests/known_related_objects/__init__.py b/tests/app_loading/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/known_related_objects/__init__.py +++ b/tests/app_loading/__init__.py diff --git a/tests/regressiontests/app_loading/eggs/brokenapp.egg b/tests/app_loading/eggs/brokenapp.egg Binary files differindex 8aca671598..8aca671598 100644 --- a/tests/regressiontests/app_loading/eggs/brokenapp.egg +++ b/tests/app_loading/eggs/brokenapp.egg diff --git a/tests/regressiontests/app_loading/eggs/modelapp.egg b/tests/app_loading/eggs/modelapp.egg Binary files differindex c2370b5b4c..c2370b5b4c 100644 --- a/tests/regressiontests/app_loading/eggs/modelapp.egg +++ b/tests/app_loading/eggs/modelapp.egg diff --git a/tests/regressiontests/app_loading/eggs/nomodelapp.egg b/tests/app_loading/eggs/nomodelapp.egg Binary files differindex 5b8d2170a2..5b8d2170a2 100644 --- a/tests/regressiontests/app_loading/eggs/nomodelapp.egg +++ b/tests/app_loading/eggs/nomodelapp.egg diff --git a/tests/regressiontests/app_loading/eggs/omelet.egg b/tests/app_loading/eggs/omelet.egg Binary files differindex bd1c68764b..bd1c68764b 100644 --- a/tests/regressiontests/app_loading/eggs/omelet.egg +++ b/tests/app_loading/eggs/omelet.egg diff --git a/tests/modeltests/invalid_models/models.py b/tests/app_loading/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/app_loading/models.py diff --git a/tests/modeltests/lookup/__init__.py b/tests/app_loading/not_installed/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/lookup/__init__.py +++ b/tests/app_loading/not_installed/__init__.py diff --git a/tests/regressiontests/app_loading/not_installed/models.py b/tests/app_loading/not_installed/models.py index 1e4b59894c..1e4b59894c 100644 --- a/tests/regressiontests/app_loading/not_installed/models.py +++ b/tests/app_loading/not_installed/models.py diff --git a/tests/regressiontests/app_loading/tests.py b/tests/app_loading/tests.py index 6dd0be2194..6dd0be2194 100644 --- a/tests/regressiontests/app_loading/tests.py +++ b/tests/app_loading/tests.py diff --git a/tests/modeltests/m2m_and_m2o/__init__.py b/tests/backends/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/m2m_and_m2o/__init__.py +++ b/tests/backends/__init__.py diff --git a/tests/regressiontests/backends/models.py b/tests/backends/models.py index 344cf4c798..4f03ddeacc 100644 --- a/tests/regressiontests/backends/models.py +++ b/tests/backends/models.py @@ -39,7 +39,7 @@ if connection.features.supports_long_model_names: verbose_name = 'model_with_long_table_name' primary_key_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.AutoField(primary_key=True) charfield_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.CharField(max_length=100) - m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.ManyToManyField(Person,blank=True) + m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.ManyToManyField(Person, blank=True) class Tag(models.Model): @@ -62,6 +62,7 @@ class Post(models.Model): class Reporter(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) + raw_data = models.BinaryField() def __str__(self): return "%s %s" % (self.first_name, self.last_name) @@ -75,3 +76,30 @@ class Article(models.Model): def __str__(self): return self.headline + + +@python_2_unicode_compatible +class Item(models.Model): + name = models.CharField(max_length=30) + date = models.DateField() + time = models.TimeField() + last_modified = models.DateTimeField() + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class Object(models.Model): + related_objects = models.ManyToManyField("self", db_constraint=False, symmetrical=False) + + def __str__(self): + return str(self.id) + + +@python_2_unicode_compatible +class ObjectReference(models.Model): + obj = models.ForeignKey(Object, db_constraint=False) + + def __str__(self): + return str(self.obj_id) diff --git a/tests/regressiontests/backends/tests.py b/tests/backends/tests.py index 934c37d147..a426926cef 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/backends/tests.py @@ -3,22 +3,23 @@ from __future__ import absolute_import, unicode_literals import datetime +from decimal import Decimal import threading from django.conf import settings from django.core.management.color import no_style -from django.core.exceptions import ImproperlyConfigured from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS, - IntegrityError, transaction) + DatabaseError, IntegrityError, transaction) from django.db.backends.signals import connection_created from django.db.backends.postgresql_psycopg2 import version as pg_version -from django.db.utils import ConnectionHandler, DatabaseError, load_backend +from django.db.backends.util import format_number +from django.db.models import Sum, Avg, Variance, StdDev +from django.db.utils import ConnectionHandler from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature, TransactionTestCase) -from django.test.utils import override_settings -from django.utils import six +from django.test.utils import override_settings, str_prefix +from django.utils import six, unittest from django.utils.six.moves import xrange -from django.utils import unittest from . import models @@ -42,7 +43,7 @@ class OracleChecks(unittest.TestCase): # Check that '%' chars are escaped for query execution. name = '"SOME%NAME"' quoted_name = connection.ops.quote_name(name) - self.assertEquals(quoted_name % (), name) + self.assertEqual(quoted_name % (), name) @unittest.skipUnless(connection.vendor == 'oracle', "No need to check Oracle cursor semantics") @@ -52,7 +53,7 @@ class OracleChecks(unittest.TestCase): convert_unicode = backend.convert_unicode cursor = connection.cursor() cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'), - [convert_unicode('_django_testing!'),]) + [convert_unicode('_django_testing!')]) @unittest.skipUnless(connection.vendor == 'oracle', "No need to check Oracle cursor semantics") @@ -72,7 +73,7 @@ class OracleChecks(unittest.TestCase): c = connection.cursor() c.execute('CREATE TABLE ltext ("TEXT" NCLOB)') long_str = ''.join([six.text_type(x) for x in xrange(4000)]) - c.execute('INSERT INTO ltext VALUES (%s)',[long_str]) + c.execute('INSERT INTO ltext VALUES (%s)', [long_str]) c.execute('SELECT text FROM ltext') row = c.fetchone() self.assertEqual(long_str, row[0].read()) @@ -99,6 +100,7 @@ class OracleChecks(unittest.TestCase): c.execute(query) self.assertEqual(c.fetchone()[0], 1) + class MySQLTests(TestCase): @unittest.skipUnless(connection.vendor == 'mysql', "Test valid only for MySQL") @@ -117,17 +119,11 @@ class MySQLTests(TestCase): found_reset = False for sql in statements: found_reset = found_reset or 'ALTER TABLE' in sql - if connection.mysql_version < (5,0,13): + if connection.mysql_version < (5, 0, 13): self.assertTrue(found_reset) else: self.assertFalse(found_reset) - @unittest.skipUnless(connection.vendor == 'mysql', - "Test valid only for MySQL") - def test_server_version_connections(self): - connection.close() - connection.mysql_version - self.assertTrue(connection.connection is None) class DateQuotingTest(TestCase): @@ -143,11 +139,11 @@ class DateQuotingTest(TestCase): 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)]) + self.assertEqual(list(years), [datetime.date(2010, 1, 1)]) - def test_django_extract(self): + def test_django_date_extract(self): """ - Test the custom ``django_extract method``, in particular against fields + Test the custom ``django_date_extract method``, in particular against fields which clash with strings passed to it (e.g. 'day') - see #12818__. __: http://code.djangoproject.com/ticket/12818 @@ -159,24 +155,35 @@ class DateQuotingTest(TestCase): self.assertEqual(len(classes), 1) +@override_settings(DEBUG=True) class LastExecutedQueryTest(TestCase): - @override_settings(DEBUG=True) + def test_debug_sql(self): - list(models.Tag.objects.filter(name="test")) + list(models.Reporter.objects.filter(first_name="test")) sql = connection.queries[-1]['sql'].lower() - self.assertTrue(sql.startswith("select")) - self.assertIn(models.Tag._meta.db_table, sql) + self.assertIn("select", sql) + self.assertIn(models.Reporter._meta.db_table, sql) def test_query_encoding(self): """ Test that last_executed_query() returns an Unicode string """ - tags = models.Tag.objects.extra(select={'föö':1}) - sql, params = tags.query.sql_with_params() - cursor = tags.query.get_compiler('default').execute_sql(None) + persons = models.Reporter.objects.filter(raw_data=b'\x00\x46 \xFE').extra(select={'föö': 1}) + sql, params = persons.query.sql_with_params() + cursor = persons.query.get_compiler('default').execute_sql(None) last_sql = cursor.db.ops.last_executed_query(cursor, sql, params) self.assertTrue(isinstance(last_sql, six.text_type)) + @unittest.skipUnless(connection.vendor == 'sqlite', + "This test is specific to SQLite.") + def test_no_interpolation_on_sqlite(self): + # Regression for #17158 + # This shouldn't raise an exception + query = "SELECT strftime('%Y', 'now');" + connection.cursor().execute(query) + self.assertEqual(connection.queries[-1]['sql'], + str_prefix("QUERY = %(_)s\"SELECT strftime('%%Y', 'now');\" - PARAMS = ()")) + class ParameterHandlingTest(TestCase): def test_bad_parameter_count(self): @@ -187,8 +194,9 @@ class ParameterHandlingTest(TestCase): 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,),]) + self.assertRaises(Exception, cursor.executemany, query, [(1, 2, 3)]) + self.assertRaises(Exception, cursor.executemany, query, [(1,)]) + # Unfortunately, the following tests would be a good test to run on all # backends, but it breaks MySQL hard. Until #13711 is fixed, it can't be run @@ -236,6 +244,7 @@ class LongNameTest(TestCase): for statement in connection.ops.sql_flush(no_style(), tables, sequences): cursor.execute(statement) + class SequenceResetTest(TestCase): def test_generic_relation(self): "Sequence names are correct when resetting generic relations (Ref #13941)" @@ -253,6 +262,7 @@ class SequenceResetTest(TestCase): obj = models.Post.objects.create(name='New post', text='goodbye world') self.assertTrue(obj.pk > 10) + class PostgresVersionTest(TestCase): def assert_parses(self, version_string, version): self.assertEqual(pg_version._parse_version(version_string), version) @@ -287,14 +297,15 @@ class PostgresVersionTest(TestCase): conn = OlderConnectionMock() self.assertEqual(pg_version.get_version(conn), 80300) + class PostgresNewConnectionTest(TestCase): """ #17062: PostgreSQL shouldn't roll back SET TIME ZONE, even if the first transaction is rolled back. """ @unittest.skipUnless( - connection.vendor == 'postgresql' and connection.isolation_level > 0, - "This test applies only to PostgreSQL without autocommit") + connection.vendor == 'postgresql', + "This test applies only to PostgreSQL") def test_connect_and_rollback(self): new_connections = ConnectionHandler(settings.DATABASES) new_connection = new_connections[DEFAULT_DB_ALIAS] @@ -327,40 +338,75 @@ class PostgresNewConnectionTest(TestCase): pass -# 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. -class ConnectionCreatedSignalTest(TestCase): +# This test needs to run outside of a transaction, otherwise closing the +# connection would implicitly rollback and cause problems during teardown. +class ConnectionCreatedSignalTest(TransactionTestCase): + + # Unfortunately with sqlite3 the in-memory test database cannot be closed, + # and so it cannot be re-opened during testing. @skipUnlessDBFeature('test_db_allows_multiple_connections') def test_signal(self): data = {} + def receiver(sender, connection, **kwargs): data["connection"] = connection connection_created.connect(receiver) connection.close() - cursor = connection.cursor() + connection.cursor() self.assertTrue(data["connection"].connection is connection.connection) connection_created.disconnect(receiver) data.clear() - cursor = connection.cursor() + connection.cursor() self.assertTrue(data == {}) class EscapingChecks(TestCase): + """ + All tests in this test case are also run with settings.DEBUG=True in + EscapingChecksDebug test case, to also test CursorDebugWrapper. + """ + def test_paramless_no_escaping(self): + cursor = connection.cursor() + cursor.execute("SELECT '%s'") + self.assertEqual(cursor.fetchall()[0][0], '%s') + + def test_parameter_escaping(self): + cursor = connection.cursor() + cursor.execute("SELECT '%%', %s", ('%d',)) + self.assertEqual(cursor.fetchall()[0], ('%', '%d')) @unittest.skipUnless(connection.vendor == 'sqlite', "This is a sqlite-specific issue") - def test_parameter_escaping(self): + def test_sqlite_parameter_escaping(self): #13648: '%s' escaping support for sqlite3 cursor = connection.cursor() - response = cursor.execute( - "select strftime('%%s', date('now'))").fetchall()[0][0] - self.assertNotEqual(response, None) + cursor.execute("select strftime('%s', date('now'))") + response = cursor.fetchall()[0][0] # response should be an non-zero integer self.assertTrue(int(response)) +@override_settings(DEBUG=True) +class EscapingChecksDebug(EscapingChecks): + pass + + +class SqlliteAggregationTests(TestCase): + """ + #19360: Raise NotImplementedError when aggregating on date/time fields. + """ + @unittest.skipUnless(connection.vendor == 'sqlite', + "No need to check SQLite aggregation semantics") + def test_aggregation(self): + for aggregate in (Sum, Avg, Variance, StdDev): + self.assertRaises(NotImplementedError, + models.Item.objects.all().aggregate, aggregate('time')) + self.assertRaises(NotImplementedError, + models.Item.objects.all().aggregate, aggregate('date')) + self.assertRaises(NotImplementedError, + models.Item.objects.all().aggregate, aggregate('last_modified')) + class BackendTestCase(TestCase): @@ -400,7 +446,6 @@ class BackendTestCase(TestCase): self.create_squares_with_executemany(args) self.assertEqual(models.Square.objects.count(), 9) - def test_unicode_fetches(self): #6254: fetchone, fetchmany, fetchall return strings as unicode objects qn = connection.ops.quote_name @@ -424,8 +469,8 @@ class BackendTestCase(TestCase): old_password = connection.settings_dict['PASSWORD'] connection.settings_dict['PASSWORD'] = "françois" try: - cursor = connection.cursor() - except backend.Database.DatabaseError: + connection.cursor() + except DatabaseError: # As password is probably wrong, a database exception is expected pass except Exception as e: @@ -451,6 +496,7 @@ class BackendTestCase(TestCase): with self.assertRaises(DatabaseError): cursor.execute(query) + # 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). @@ -458,7 +504,6 @@ class BackendTestCase(TestCase): # 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): @@ -497,7 +542,8 @@ class FkConstraintsTests(TransactionTestCase): """ When constraint checks are disabled, should be able to write bad data without IntegrityErrors. """ - with transaction.commit_manually(): + transaction.set_autocommit(False) + try: # 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 @@ -511,12 +557,15 @@ class FkConstraintsTests(TransactionTestCase): self.fail("IntegrityError should not have occurred.") finally: transaction.rollback() + finally: + transaction.set_autocommit(True) def test_disable_constraint_checks_context_manager(self): """ When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors. """ - with transaction.commit_manually(): + transaction.set_autocommit(False) + try: # 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 @@ -529,12 +578,15 @@ class FkConstraintsTests(TransactionTestCase): self.fail("IntegrityError should not have occurred.") finally: transaction.rollback() + finally: + transaction.set_autocommit(True) def test_check_constraints(self): """ Constraint checks should raise an IntegrityError when bad data is in the DB. """ - with transaction.commit_manually(): + try: + transaction.set_autocommit(False) # 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 @@ -547,6 +599,8 @@ class FkConstraintsTests(TransactionTestCase): connection.check_constraints() finally: transaction.rollback() + finally: + transaction.set_autocommit(True) class ThreadTests(TestCase): @@ -557,9 +611,12 @@ class ThreadTests(TestCase): different for each thread. Refs #17258. """ - connections_set = set() + # Map connections by id because connections with identical aliases + # have the same hash. + connections_dict = {} connection.cursor() - connections_set.add(connection) + connections_dict[id(connection)] = connection + def runner(): # Passing django.db.connection between threads doesn't work while # connections[DEFAULT_DB_ALIAS] does. @@ -569,19 +626,19 @@ class ThreadTests(TestCase): # main thread. connection.allow_thread_sharing = True connection.cursor() - connections_set.add(connection) + connections_dict[id(connection)] = connection for x in range(2): t = threading.Thread(target=runner) t.start() t.join() # Check that each created connection got different inner connection. self.assertEqual( - len(set([conn.connection for conn in connections_set])), + len(set(conn.connection for conn in connections_dict.values())), 3) # Finish by closing the connections opened by the other threads (the # connection opened in the main thread will automatically be closed on # teardown). - for conn in connections_set: + for conn in connections_dict.values(): if conn is not connection: conn.close() @@ -590,25 +647,28 @@ class ThreadTests(TestCase): Ensure that the connections are different for each thread. Refs #17258. """ - connections_set = set() + # Map connections by id because connections with identical aliases + # have the same hash. + connections_dict = {} for conn in connections.all(): - connections_set.add(conn) + connections_dict[id(conn)] = conn + def runner(): from django.db import connections for conn in connections.all(): # Allow thread sharing so the connection can be closed by the # main thread. conn.allow_thread_sharing = True - connections_set.add(conn) + connections_dict[id(conn)] = conn for x in range(2): t = threading.Thread(target=runner) t.start() t.join() - self.assertEqual(len(connections_set), 6) + self.assertEqual(len(connections_dict), 6) # Finish by closing the connections opened by the other threads (the # connection opened in the main thread will automatically be closed on # teardown). - for conn in connections_set: + for conn in connections_dict.values(): if conn is not connection: conn.close() @@ -659,6 +719,7 @@ class ThreadTests(TestCase): """ # First, without explicitly enabling the connection for sharing. exceptions = set() + def runner1(): def runner2(other_thread_connection): try: @@ -676,6 +737,7 @@ class ThreadTests(TestCase): # Then, with explicitly enabling the connection for sharing. exceptions = set() + def runner1(): def runner2(other_thread_connection): try: @@ -704,3 +766,72 @@ class MySQLPKZeroTests(TestCase): def test_zero_as_autoval(self): with self.assertRaises(ValueError): models.Square.objects.create(id=0, root=0, square=1) + + +class DBConstraintTestCase(TransactionTestCase): + def test_can_reference_existant(self): + obj = models.Object.objects.create() + ref = models.ObjectReference.objects.create(obj=obj) + self.assertEqual(ref.obj, obj) + + ref = models.ObjectReference.objects.get(obj=obj) + self.assertEqual(ref.obj, obj) + + def test_can_reference_non_existant(self): + self.assertFalse(models.Object.objects.filter(id=12345).exists()) + ref = models.ObjectReference.objects.create(obj_id=12345) + ref_new = models.ObjectReference.objects.get(obj_id=12345) + self.assertEqual(ref, ref_new) + + with self.assertRaises(models.Object.DoesNotExist): + ref.obj + + def test_many_to_many(self): + obj = models.Object.objects.create() + obj.related_objects.create() + self.assertEqual(models.Object.objects.count(), 2) + self.assertEqual(obj.related_objects.count(), 1) + + intermediary_model = models.Object._meta.get_field_by_name("related_objects")[0].rel.through + intermediary_model.objects.create(from_object_id=obj.id, to_object_id=12345) + self.assertEqual(obj.related_objects.count(), 1) + self.assertEqual(intermediary_model.objects.count(), 2) + + +class BackendUtilTests(TestCase): + + def test_format_number(self): + """ + Test the format_number converter utility + """ + def equal(value, max_d, places, result): + self.assertEqual(format_number(Decimal(value), max_d, places), result) + + equal('0', 12, 3, + '0.000') + equal('0', 12, 8, + '0.00000000') + equal('1', 12, 9, + '1.000000000') + equal('0.00000000', 12, 8, + '0.00000000') + equal('0.000000004', 12, 8, + '0.00000000') + equal('0.000000008', 12, 8, + '0.00000001') + equal('0.000000000000000000999', 10, 8, + '0.00000000') + equal('0.1234567890', 12, 10, + '0.1234567890') + equal('0.1234567890', 12, 9, + '0.123456789') + equal('0.1234567890', 12, 8, + '0.12345679') + equal('0.1234567890', 12, 5, + '0.12346') + equal('0.1234567890', 12, 3, + '0.123') + equal('0.1234567890', 12, 1, + '0.1') + equal('0.1234567890', 12, 0, + '0') diff --git a/tests/modeltests/m2m_intermediary/__init__.py b/tests/base/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/m2m_intermediary/__init__.py +++ b/tests/base/__init__.py diff --git a/tests/base/models.py b/tests/base/models.py new file mode 100644 index 0000000000..bddb406820 --- /dev/null +++ b/tests/base/models.py @@ -0,0 +1,23 @@ +from __future__ import unicode_literals + +from django.db import models +from django.utils import six + + +# The models definitions below used to crash. Generating models dynamically +# at runtime is a bad idea because it pollutes the app cache. This doesn't +# integrate well with the test suite but at least it prevents regressions. + + +class CustomBaseModel(models.base.ModelBase): + pass + + +class MyModel(six.with_metaclass(CustomBaseModel, models.Model)): + """Model subclass with a custom base using six.with_metaclass.""" + + +if not six.PY3: + class MyModel(models.Model): + """Model subclass with a custom base using __metaclass__.""" + __metaclass__ = CustomBaseModel diff --git a/tests/modeltests/files/__init__.py b/tests/bash_completion/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/modeltests/files/__init__.py +++ b/tests/bash_completion/__init__.py diff --git a/tests/modeltests/invalid_models/__init__.py b/tests/bash_completion/management/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/modeltests/invalid_models/__init__.py +++ b/tests/bash_completion/management/__init__.py diff --git a/tests/modeltests/m2m_signals/__init__.py b/tests/bash_completion/management/commands/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/modeltests/m2m_signals/__init__.py +++ b/tests/bash_completion/management/commands/__init__.py diff --git a/tests/regressiontests/bash_completion/management/commands/test_command.py b/tests/bash_completion/management/commands/test_command.py index 0f5750645c..0f5750645c 100644 --- a/tests/regressiontests/bash_completion/management/commands/test_command.py +++ b/tests/bash_completion/management/commands/test_command.py diff --git a/tests/regressiontests/bash_completion/models.py b/tests/bash_completion/models.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/bash_completion/models.py +++ b/tests/bash_completion/models.py diff --git a/tests/regressiontests/bash_completion/tests.py b/tests/bash_completion/tests.py index ed8cedf1ab..4fdb793feb 100644 --- a/tests/regressiontests/bash_completion/tests.py +++ b/tests/bash_completion/tests.py @@ -66,7 +66,7 @@ class BashCompletionTests(unittest.TestCase): "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 sqlsequencereset']) + self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqldropindexes sqlflush sqlindexes sqlinitialdata sqlsequencereset']) def test_help(self): "No errors, just an empty list if there are no autocomplete options" diff --git a/tests/modeltests/m2m_multiple/__init__.py b/tests/basic/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/m2m_multiple/__init__.py +++ b/tests/basic/__init__.py diff --git a/tests/modeltests/basic/models.py b/tests/basic/models.py index 660beddf49..660beddf49 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/basic/models.py diff --git a/tests/modeltests/basic/tests.py b/tests/basic/tests.py index 1c83b980a7..ccbb9bd423 100644 --- a/tests/modeltests/basic/tests.py +++ b/tests/basic/tests.py @@ -1,10 +1,13 @@ from __future__ import absolute_import, unicode_literals from datetime import datetime +import threading from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned +from django.db import connections, DEFAULT_DB_ALIAS from django.db.models.fields import Field, FieldDoesNotExist -from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature +from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet +from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils import six from django.utils.translation import ugettext_lazy @@ -140,21 +143,21 @@ class ModelTest(TestCase): # Django raises an Article.MultipleObjectsReturned exception if the # lookup matches more than one object - self.assertRaisesRegexp( + six.assertRaisesRegex(self, MultipleObjectsReturned, "get\(\) returned more than one Article -- it returned 2!", Article.objects.get, headline__startswith='Area', ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, MultipleObjectsReturned, "get\(\) returned more than one Article -- it returned 2!", Article.objects.get, pub_date__year=2005, ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, MultipleObjectsReturned, "get\(\) returned more than one Article -- it returned 2!", Article.objects.get, @@ -265,34 +268,34 @@ class ModelTest(TestCase): # ... but there will often be more efficient ways if that is all you need: self.assertTrue(Article.objects.filter(id=a8.id).exists()) - # dates() returns a list of available dates of the given scope for + # datetimes() returns a list of available dates of the given scope for # the given field. self.assertQuerysetEqual( - Article.objects.dates('pub_date', 'year'), + Article.objects.datetimes('pub_date', 'year'), ["datetime.datetime(2005, 1, 1, 0, 0)"]) self.assertQuerysetEqual( - Article.objects.dates('pub_date', 'month'), + Article.objects.datetimes('pub_date', 'month'), ["datetime.datetime(2005, 7, 1, 0, 0)"]) self.assertQuerysetEqual( - Article.objects.dates('pub_date', 'day'), + Article.objects.datetimes('pub_date', 'day'), ["datetime.datetime(2005, 7, 28, 0, 0)", "datetime.datetime(2005, 7, 29, 0, 0)", "datetime.datetime(2005, 7, 30, 0, 0)", "datetime.datetime(2005, 7, 31, 0, 0)"]) self.assertQuerysetEqual( - Article.objects.dates('pub_date', 'day', order='ASC'), + Article.objects.datetimes('pub_date', 'day', order='ASC'), ["datetime.datetime(2005, 7, 28, 0, 0)", "datetime.datetime(2005, 7, 29, 0, 0)", "datetime.datetime(2005, 7, 30, 0, 0)", "datetime.datetime(2005, 7, 31, 0, 0)"]) self.assertQuerysetEqual( - Article.objects.dates('pub_date', 'day', order='DESC'), + Article.objects.datetimes('pub_date', 'day', order='DESC'), ["datetime.datetime(2005, 7, 31, 0, 0)", "datetime.datetime(2005, 7, 30, 0, 0)", "datetime.datetime(2005, 7, 29, 0, 0)", "datetime.datetime(2005, 7, 28, 0, 0)"]) - # dates() requires valid arguments. + # datetimes() requires valid arguments. self.assertRaises( TypeError, Article.objects.dates, @@ -323,10 +326,10 @@ class ModelTest(TestCase): order="bad order", ) - # Use iterator() with dates() to return a generator that lazily + # Use iterator() with datetimes() to return a generator that lazily # requests each result one at a time, to save memory. dates = [] - for article in Article.objects.dates('pub_date', 'day', order='DESC').iterator(): + for article in Article.objects.datetimes('pub_date', 'day', order='DESC').iterator(): dates.append(article) self.assertEqual(dates, [ datetime(2005, 7, 31, 0, 0), @@ -639,3 +642,74 @@ class ModelTest(TestCase): Article.objects.bulk_create([Article(headline=lazy, pub_date=datetime.now())]) article = Article.objects.get() self.assertEqual(article.headline, notlazy) + + def test_emptyqs(self): + # Can't be instantiated + with self.assertRaises(TypeError): + EmptyQuerySet() + self.assertTrue(isinstance(Article.objects.none(), EmptyQuerySet)) + + def test_emptyqs_values(self): + # test for #15959 + Article.objects.create(headline='foo', pub_date=datetime.now()) + with self.assertNumQueries(0): + qs = Article.objects.none().values_list('pk') + self.assertTrue(isinstance(qs, EmptyQuerySet)) + self.assertTrue(isinstance(qs, ValuesListQuerySet)) + self.assertEqual(len(qs), 0) + + def test_emptyqs_customqs(self): + # A hacky test for custom QuerySet subclass - refs #17271 + Article.objects.create(headline='foo', pub_date=datetime.now()) + class CustomQuerySet(QuerySet): + def do_something(self): + return 'did something' + + qs = Article.objects.all() + qs.__class__ = CustomQuerySet + qs = qs.none() + with self.assertNumQueries(0): + self.assertEqual(len(qs), 0) + self.assertTrue(isinstance(qs, EmptyQuerySet)) + self.assertEqual(qs.do_something(), 'did something') + + def test_emptyqs_values_order(self): + # Tests for ticket #17712 + Article.objects.create(headline='foo', pub_date=datetime.now()) + with self.assertNumQueries(0): + self.assertEqual(len(Article.objects.none().values_list('id').order_by('id')), 0) + with self.assertNumQueries(0): + self.assertEqual(len(Article.objects.none().filter( + id__in=Article.objects.values_list('id', flat=True))), 0) + + @skipUnlessDBFeature('can_distinct_on_fields') + def test_emptyqs_distinct(self): + # Tests for #19426 + Article.objects.create(headline='foo', pub_date=datetime.now()) + with self.assertNumQueries(0): + self.assertEqual(len(Article.objects.none().distinct('headline', 'pub_date')), 0) + +class ConcurrentSaveTests(TransactionTestCase): + @skipUnlessDBFeature('test_db_allows_multiple_connections') + def test_concurrent_delete_with_save(self): + """ + Test fetching, deleting and finally saving an object - we should get + an insert in this case. + """ + a = Article.objects.create(headline='foo', pub_date=datetime.now()) + exceptions = [] + def deleter(): + try: + # Do not delete a directly - doing so alters its state. + Article.objects.filter(pk=a.pk).delete() + connections[DEFAULT_DB_ALIAS].commit_unless_managed() + except Exception as e: + exceptions.append(e) + finally: + connections[DEFAULT_DB_ALIAS].close() + self.assertEqual(len(exceptions), 0) + t = threading.Thread(target=deleter) + t.start() + t.join() + a.save() + self.assertEqual(Article.objects.get(pk=a.pk).headline, 'foo') diff --git a/tests/modeltests/m2m_recursive/__init__.py b/tests/bug639/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/m2m_recursive/__init__.py +++ b/tests/bug639/__init__.py diff --git a/tests/regressiontests/bug639/models.py b/tests/bug639/models.py index e641555c87..e641555c87 100644 --- a/tests/regressiontests/bug639/models.py +++ b/tests/bug639/models.py diff --git a/tests/regressiontests/bug639/test.jpg b/tests/bug639/test.jpg Binary files differindex 391b57a0f3..391b57a0f3 100644 --- a/tests/regressiontests/bug639/test.jpg +++ b/tests/bug639/test.jpg diff --git a/tests/regressiontests/bug639/tests.py b/tests/bug639/tests.py index fcc1e0f7d1..fcc1e0f7d1 100644 --- a/tests/regressiontests/bug639/tests.py +++ b/tests/bug639/tests.py diff --git a/tests/modeltests/m2o_recursive/__init__.py b/tests/bug8245/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/m2o_recursive/__init__.py +++ b/tests/bug8245/__init__.py diff --git a/tests/regressiontests/bug8245/admin.py b/tests/bug8245/admin.py index d821190763..d821190763 100644 --- a/tests/regressiontests/bug8245/admin.py +++ b/tests/bug8245/admin.py diff --git a/tests/regressiontests/bug8245/models.py b/tests/bug8245/models.py index a093db4bd6..a093db4bd6 100644 --- a/tests/regressiontests/bug8245/models.py +++ b/tests/bug8245/models.py diff --git a/tests/regressiontests/bug8245/tests.py b/tests/bug8245/tests.py index 5da0b1f718..5da0b1f718 100644 --- a/tests/regressiontests/bug8245/tests.py +++ b/tests/bug8245/tests.py diff --git a/tests/modeltests/many_to_many/__init__.py b/tests/builtin_server/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/many_to_many/__init__.py +++ b/tests/builtin_server/__init__.py diff --git a/tests/modeltests/validators/models.py b/tests/builtin_server/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/validators/models.py +++ b/tests/builtin_server/models.py diff --git a/tests/builtin_server/tests.py b/tests/builtin_server/tests.py new file mode 100644 index 0000000000..662466a110 --- /dev/null +++ b/tests/builtin_server/tests.py @@ -0,0 +1,102 @@ +from __future__ import unicode_literals + +from io import BytesIO + +from django.core.servers.basehttp import ServerHandler, MAX_SOCKET_CHUNK_SIZE +from django.utils.unittest import TestCase + + +class DummyHandler(object): + def log_request(self, *args, **kwargs): + pass + + +class FileWrapperHandler(ServerHandler): + def __init__(self, *args, **kwargs): + super(FileWrapperHandler, self).__init__(*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(str('200 OK'), [(str('Content-Type'), str('text/plain'))]) + return [b'Hello World!'] + + +def wsgi_app_file_wrapper(environ, start_response): + start_response(str('200 OK'), [(str('Content-Type'), str('text/plain'))]) + return environ['wsgi.file_wrapper'](BytesIO(b'foo')) + + +class WSGIFileWrapperTests(TestCase): + """ + Test that the wsgi.file_wrapper works for the builting server. + + Tests for #9659: wsgi.file_wrapper in the builtin server. + We need to mock a couple of handlers and keep track of what + gets called when using a couple kinds of WSGI apps. + """ + + def test_file_wrapper_uses_sendfile(self): + env = {'SERVER_PROTOCOL': 'HTTP/1.0'} + handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env) + handler.run(wsgi_app_file_wrapper) + self.assertTrue(handler._used_sendfile) + self.assertEqual(handler.stdout.getvalue(), b'') + self.assertEqual(handler.stderr.getvalue(), b'') + + def test_file_wrapper_no_sendfile(self): + env = {'SERVER_PROTOCOL': 'HTTP/1.0'} + handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env) + handler.run(wsgi_app) + self.assertFalse(handler._used_sendfile) + self.assertEqual(handler.stdout.getvalue().splitlines()[-1], b'Hello World!') + self.assertEqual(handler.stderr.getvalue(), b'') + + +class WriteChunkCounterHandler(ServerHandler): + """ + Server handler that counts the number of chunks written after headers were + sent. Used to make sure large response body chunking works properly. + """ + + def __init__(self, *args, **kwargs): + super(WriteChunkCounterHandler, self).__init__(*args, **kwargs) + self.request_handler = DummyHandler() + self.headers_written = False + self.write_chunk_counter = 0 + + def send_headers(self): + super(WriteChunkCounterHandler, self).send_headers() + self.headers_written = True + + def _write(self, data): + if self.headers_written: + self.write_chunk_counter += 1 + self.stdout.write(data) + + +def send_big_data_app(environ, start_response): + start_response(str('200 OK'), [(str('Content-Type'), str('text/plain'))]) + # Return a blob of data that is 1.5 times the maximum chunk size. + return [b'x' * (MAX_SOCKET_CHUNK_SIZE + MAX_SOCKET_CHUNK_SIZE // 2)] + + +class ServerHandlerChunksProperly(TestCase): + """ + Test that the ServerHandler chunks data properly. + + Tests for #18972: The logic that performs the math to break data into + 32MB (MAX_SOCKET_CHUNK_SIZE) chunks was flawed, BUT it didn't actually + cause any problems. + """ + + def test_chunked_data(self): + env = {'SERVER_PROTOCOL': 'HTTP/1.0'} + handler = WriteChunkCounterHandler(None, BytesIO(), BytesIO(), env) + handler.run(send_big_data_app) + self.assertEqual(handler.write_chunk_counter, 2) diff --git a/tests/modeltests/many_to_one/__init__.py b/tests/bulk_create/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/many_to_one/__init__.py +++ b/tests/bulk_create/__init__.py diff --git a/tests/regressiontests/bulk_create/models.py b/tests/bulk_create/models.py index bc685bbbe4..bc685bbbe4 100644 --- a/tests/regressiontests/bulk_create/models.py +++ b/tests/bulk_create/models.py diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/bulk_create/tests.py index d4772934a1..d4772934a1 100644 --- a/tests/regressiontests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py diff --git a/tests/modeltests/many_to_one_null/__init__.py b/tests/cache/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/many_to_one_null/__init__.py +++ b/tests/cache/__init__.py diff --git a/tests/regressiontests/cache/closeable_cache.py b/tests/cache/closeable_cache.py index 83073850b7..83073850b7 100644 --- a/tests/regressiontests/cache/closeable_cache.py +++ b/tests/cache/closeable_cache.py diff --git a/tests/regressiontests/cache/liberal_backend.py b/tests/cache/liberal_backend.py index b72013c6a8..b72013c6a8 100644 --- a/tests/regressiontests/cache/liberal_backend.py +++ b/tests/cache/liberal_backend.py diff --git a/tests/regressiontests/cache/models.py b/tests/cache/models.py index 2cd648b780..2cd648b780 100644 --- a/tests/regressiontests/cache/models.py +++ b/tests/cache/models.py diff --git a/tests/regressiontests/cache/tests.py b/tests/cache/tests.py index fc92ecf870..00c51638b7 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/cache/tests.py @@ -12,13 +12,15 @@ import string import tempfile import time import warnings +import pickle from django.conf import settings from django.core import management from django.core.cache import get_cache from django.core.cache.backends.base import (CacheKeyWarning, InvalidCacheBackendError) -from django.db import router +from django.db import router, transaction +from django.core.cache.utils import make_template_fragment_key from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse, QueryDict) from django.middleware.cache import (FetchFromCacheMiddleware, @@ -807,7 +809,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): self.prefix_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix') self.v2_cache = get_cache(self.backend_name, LOCATION=self._table_name, VERSION=2) self.custom_key_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='regressiontests.cache.tests.custom_key_func') + self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func') def tearDown(self): from django.db import connection @@ -836,6 +838,13 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): interactive=False ) + def test_clear_commits_transaction(self): + # Ensure the database transaction is committed (#19896) + self.cache.set("key1", "spam") + self.cache.clear() + transaction.rollback() + self.assertEqual(self.cache.get("key1"), None) + @override_settings(USE_TZ=True) class DBCacheWithTimeZoneTests(DBCacheTests): @@ -888,7 +897,7 @@ class LocMemCacheTests(unittest.TestCase, BaseCacheTests): self.prefix_cache = get_cache(self.backend_name, KEY_PREFIX='cacheprefix') self.v2_cache = get_cache(self.backend_name, VERSION=2) self.custom_key_cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION='regressiontests.cache.tests.custom_key_func') + self.custom_key_cache2 = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION='cache.tests.custom_key_func') # LocMem requires a hack to make the other caches # share a data store with the 'normal' cache. @@ -957,7 +966,7 @@ class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): self.prefix_cache = get_cache(cache_key, KEY_PREFIX=random_prefix) self.v2_cache = get_cache(cache_key, VERSION=2) self.custom_key_cache = get_cache(cache_key, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(cache_key, KEY_FUNCTION='regressiontests.cache.tests.custom_key_func') + self.custom_key_cache2 = get_cache(cache_key, KEY_FUNCTION='cache.tests.custom_key_func') def tearDown(self): self.cache.clear() @@ -977,6 +986,18 @@ class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): # memcached limits key length to 250 self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value') + # Explicitly display a skipped test if no configured cache uses MemcachedCache + @unittest.skipUnless( + any(cache['BACKEND'] == 'django.core.cache.backends.memcached.MemcachedCache' + for cache in settings.CACHES.values()), + "cache with python-memcached library not available") + def test_memcached_uses_highest_pickle_version(self): + # Regression test for #19810 + for cache_key, cache in settings.CACHES.items(): + if cache['BACKEND'] == 'django.core.cache.backends.memcached.MemcachedCache': + self.assertEqual(get_cache(cache_key)._cache.pickleProtocol, + pickle.HIGHEST_PROTOCOL) + class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): """ @@ -990,7 +1011,7 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): self.prefix_cache = get_cache(self.backend_name, LOCATION=self.dirname, KEY_PREFIX='cacheprefix') self.v2_cache = get_cache(self.backend_name, LOCATION=self.dirname, VERSION=2) self.custom_key_cache = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION='regressiontests.cache.tests.custom_key_func') + self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION='cache.tests.custom_key_func') def tearDown(self): self.cache.clear() @@ -1034,7 +1055,7 @@ class CustomCacheKeyValidationTests(unittest.TestCase): """ def test_custom_key_validation(self): - cache = get_cache('regressiontests.cache.liberal_backend://') + cache = get_cache('cache.liberal_backend://') # this key is both longer than 250 characters, and has spaces key = 'some key with spaces' * 15 @@ -1061,7 +1082,7 @@ class GetCacheTests(unittest.TestCase): def test_close(self): from django.core import signals - cache = get_cache('regressiontests.cache.closeable_cache.CacheClass') + cache = get_cache('cache.closeable_cache.CacheClass') self.assertFalse(cache.closed) signals.request_finished.send(self.__class__) self.assertTrue(cache.closed) @@ -1303,6 +1324,73 @@ class CacheI18nTest(TestCase): key2 = get_cache_key(request) self.assertEqual(key, key2) + def check_accept_language_vary(self, accept_language, vary, reference_key): + request = self._get_request() + request.META['HTTP_ACCEPT_LANGUAGE'] = accept_language + request.META['HTTP_ACCEPT_ENCODING'] = 'gzip;q=1.0, identity; q=0.5, *;q=0' + response = HttpResponse() + response['Vary'] = vary + key = learn_cache_key(request, response) + key2 = get_cache_key(request) + self.assertEqual(key, reference_key) + self.assertEqual(key2, reference_key) + + @override_settings(USE_I18N=True, USE_L10N=False, USE_TZ=False) + def test_cache_key_i18n_translation_accept_language(self): + lang = translation.get_language() + self.assertEqual(lang, 'en') + request = self._get_request() + request.META['HTTP_ACCEPT_ENCODING'] = 'gzip;q=1.0, identity; q=0.5, *;q=0' + response = HttpResponse() + response['Vary'] = 'accept-encoding' + key = learn_cache_key(request, response) + self.assertIn(lang, key, "Cache keys should include the language name when translation is active") + self.check_accept_language_vary( + 'en-us', + 'cookie, accept-language, accept-encoding', + key + ) + self.check_accept_language_vary( + 'en-US', + 'cookie, accept-encoding, accept-language', + key + ) + self.check_accept_language_vary( + 'en-US,en;q=0.8', + 'accept-encoding, accept-language, cookie', + key + ) + self.check_accept_language_vary( + 'en-US,en;q=0.8,ko;q=0.6', + 'accept-language, cookie, accept-encoding', + key + ) + self.check_accept_language_vary( + 'ko-kr,ko;q=0.8,en-us;q=0.5,en;q=0.3 ', + 'accept-encoding, cookie, accept-language', + key + ) + self.check_accept_language_vary( + 'ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4', + 'accept-language, accept-encoding, cookie', + key + ) + self.check_accept_language_vary( + 'ko;q=1.0,en;q=0.5', + 'cookie, accept-language, accept-encoding', + key + ) + self.check_accept_language_vary( + 'ko, en', + 'cookie, accept-encoding, accept-language', + key + ) + self.check_accept_language_vary( + 'ko-KR, en-US', + 'accept-encoding, accept-language, cookie', + key + ) + @override_settings(USE_I18N=False, USE_L10N=True, USE_TZ=False) def test_cache_key_i18n_formatting(self): request = self._get_request() @@ -1428,24 +1516,21 @@ class CacheI18nTest(TestCase): CACHE_MIDDLEWARE_SECONDS=60, USE_ETAGS=True, ) - def test_middleware_with_streaming_response(self): - # cache with non empty request.GET - request = self._get_request_cache(query_string='foo=baz&other=true') - - # first access, cache must return None + def test_middleware_doesnt_cache_streaming_response(self): + request = self._get_request() get_cache_data = FetchFromCacheMiddleware().process_request(request) - self.assertEqual(get_cache_data, None) + self.assertIsNone(get_cache_data) - # pass streaming response through UpdateCacheMiddleware. - content = 'Check for cache with QUERY_STRING and streaming content' + # This test passes on Python < 3.3 even without the corresponding code + # in UpdateCacheMiddleware, because pickling a StreamingHttpResponse + # fails (http://bugs.python.org/issue14288). LocMemCache silently + # swallows the exception and doesn't store the response in cache. + content = ['Check for cache with streaming content.'] response = StreamingHttpResponse(content) UpdateCacheMiddleware().process_response(request, response) - # second access, cache must still return None, because we can't cache - # streaming response. get_cache_data = FetchFromCacheMiddleware().process_request(request) - self.assertEqual(get_cache_data, None) - + self.assertIsNone(get_cache_data) @override_settings( CACHES={ @@ -1481,19 +1566,12 @@ def hello_world_view(request, value): ) class CacheMiddlewareTest(TestCase): - # The following tests will need to be modified in Django 1.6 to not use - # deprecated ways of using the cache_page decorator that will be removed in - # such version def setUp(self): self.factory = RequestFactory() self.default_cache = get_cache('default') self.other_cache = get_cache('other') - self.save_warnings_state() - warnings.filterwarnings('ignore', category=DeprecationWarning, - module='django.views.decorators.cache') def tearDown(self): - self.restore_warnings_state() self.default_cache.clear() self.other_cache.clear() @@ -1606,21 +1684,20 @@ class CacheMiddlewareTest(TestCase): request.user = MockAuthenticatedUser() request.session = MockAccessedSession() - response = cache_page(hello_world_view)(request, '1') + response = cache_page(60)(hello_world_view)(request, '1') self.assertFalse("Cache-Control" in response) def test_view_decorator(self): # decorate the same view with different cache decorators - default_view = cache_page(hello_world_view) - default_with_prefix_view = cache_page(key_prefix='prefix1')(hello_world_view) + default_view = cache_page(3)(hello_world_view) + default_with_prefix_view = cache_page(3, key_prefix='prefix1')(hello_world_view) - explicit_default_view = cache_page(cache='default')(hello_world_view) - explicit_default_with_prefix_view = cache_page(cache='default', key_prefix='prefix1')(hello_world_view) + explicit_default_view = cache_page(3, cache='default')(hello_world_view) + explicit_default_with_prefix_view = cache_page(3, cache='default', key_prefix='prefix1')(hello_world_view) - other_view = cache_page(cache='other')(hello_world_view) - other_with_prefix_view = cache_page(cache='other', key_prefix='prefix2')(hello_world_view) - other_with_timeout_view = cache_page(3, cache='other', key_prefix='prefix3')(hello_world_view) + other_view = cache_page(1, cache='other')(hello_world_view) + other_with_prefix_view = cache_page(1, cache='other', key_prefix='prefix2')(hello_world_view) request = self.factory.get('/view/') @@ -1660,10 +1737,6 @@ class CacheMiddlewareTest(TestCase): response = other_with_prefix_view(request, '9') self.assertEqual(response.content, b'Hello World 9') - # Request from the alternate cache with a new prefix and a custom timeout - response = other_with_timeout_view(request, '10') - self.assertEqual(response.content, b'Hello World 10') - # But if we wait a couple of seconds... time.sleep(2) @@ -1692,18 +1765,6 @@ class CacheMiddlewareTest(TestCase): response = other_with_prefix_view(request, '16') self.assertEqual(response.content, b'Hello World 16') - # ... but a view with a custom timeout will still hit - response = other_with_timeout_view(request, '17') - self.assertEqual(response.content, b'Hello World 10') - - # And if we wait a few more seconds - time.sleep(2) - - # the custom timeout cache will miss - response = other_with_timeout_view(request, '18') - self.assertEqual(response.content, b'Hello World 18') - - @override_settings( CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix', CACHE_MIDDLEWARE_SECONDS=1, @@ -1804,7 +1865,7 @@ class TestWithTemplateResponse(TestCase): class TestEtagWithAdmin(TestCase): # See https://code.djangoproject.com/ticket/16003 - urls = "regressiontests.admin_views.urls" + urls = "admin_views.urls" def test_admin(self): with self.settings(USE_ETAGS=False): @@ -1816,3 +1877,25 @@ class TestEtagWithAdmin(TestCase): response = self.client.get('/test_admin/admin/') self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header('ETag')) + + +class TestMakeTemplateFragmentKey(TestCase): + def test_without_vary_on(self): + key = make_template_fragment_key('a.fragment') + self.assertEqual(key, 'template.cache.a.fragment.d41d8cd98f00b204e9800998ecf8427e') + + def test_with_one_vary_on(self): + key = make_template_fragment_key('foo', ['abc']) + self.assertEqual(key, + 'template.cache.foo.900150983cd24fb0d6963f7d28e17f72') + + def test_with_many_vary_on(self): + key = make_template_fragment_key('bar', ['abc', 'def']) + self.assertEqual(key, + 'template.cache.bar.4b35f12ab03cec09beec4c21b2d2fa88') + + def test_proper_escaping(self): + key = make_template_fragment_key('spam', ['abc:def%']) + self.assertEqual(key, + 'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469') + diff --git a/tests/modeltests/model_forms/__init__.py b/tests/choices/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/model_forms/__init__.py +++ b/tests/choices/__init__.py diff --git a/tests/modeltests/choices/models.py b/tests/choices/models.py index 2fa33a9680..2fa33a9680 100644 --- a/tests/modeltests/choices/models.py +++ b/tests/choices/models.py diff --git a/tests/modeltests/choices/tests.py b/tests/choices/tests.py index 03a7d3340d..03a7d3340d 100644 --- a/tests/modeltests/choices/tests.py +++ b/tests/choices/tests.py diff --git a/tests/modeltests/model_formsets/__init__.py b/tests/commands_sql/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/model_formsets/__init__.py +++ b/tests/commands_sql/__init__.py diff --git a/tests/commands_sql/models.py b/tests/commands_sql/models.py new file mode 100644 index 0000000000..089aa96f30 --- /dev/null +++ b/tests/commands_sql/models.py @@ -0,0 +1,7 @@ +from django.db import models +from django.utils.encoding import python_2_unicode_compatible + + +@python_2_unicode_compatible +class Book(models.Model): + title = models.CharField(max_length=100, db_index=True) diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py new file mode 100644 index 0000000000..831e9a27c4 --- /dev/null +++ b/tests/commands_sql/tests.py @@ -0,0 +1,52 @@ +from __future__ import unicode_literals + +from django.core.management.color import no_style +from django.core.management.sql import (sql_create, sql_delete, sql_indexes, + sql_destroy_indexes, sql_all) +from django.db import connections, DEFAULT_DB_ALIAS, models +from django.test import TestCase +from django.utils import six + +# See also initial_sql_regress for 'custom_sql_for_model' tests + + +class SQLCommandsTestCase(TestCase): + """Tests for several functions in django/core/management/sql.py""" + def test_sql_create(self): + app = models.get_app('commands_sql') + output = sql_create(app, no_style(), connections[DEFAULT_DB_ALIAS]) + # Lower so that Oracle's upper case tbl names wont break + sql = output[0].lower() + six.assertRegex(self, sql, r'^create table .commands_sql_book.*') + + def test_sql_delete(self): + app = models.get_app('commands_sql') + output = sql_delete(app, no_style(), connections[DEFAULT_DB_ALIAS]) + # Oracle produces DROP SEQUENCE and DROP TABLE for this command. + if connections[DEFAULT_DB_ALIAS].vendor == 'oracle': + sql = output[1].lower() + else: + sql = output[0].lower() + six.assertRegex(self, sql, r'^drop table .commands_sql_book.*') + + def test_sql_indexes(self): + app = models.get_app('commands_sql') + output = sql_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS]) + # PostgreSQL creates two indexes + self.assertIn(len(output), [1, 2]) + self.assertTrue(output[0].startswith("CREATE INDEX")) + + def test_sql_destroy_indexes(self): + app = models.get_app('commands_sql') + output = sql_destroy_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS]) + # PostgreSQL creates two indexes + self.assertIn(len(output), [1, 2]) + self.assertTrue(output[0].startswith("DROP INDEX")) + + def test_sql_all(self): + app = models.get_app('commands_sql') + output = sql_all(app, no_style(), connections[DEFAULT_DB_ALIAS]) + # PostgreSQL creates two indexes + self.assertIn(len(output), [2, 3]) + self.assertTrue(output[0].startswith('CREATE TABLE')) + self.assertTrue(output[1].startswith('CREATE INDEX')) diff --git a/tests/modeltests/model_inheritance/__init__.py b/tests/comment_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/model_inheritance/__init__.py +++ b/tests/comment_tests/__init__.py diff --git a/tests/comment_tests/custom_comments/__init__.py b/tests/comment_tests/custom_comments/__init__.py new file mode 100644 index 0000000000..14c614f610 --- /dev/null +++ b/tests/comment_tests/custom_comments/__init__.py @@ -0,0 +1,32 @@ +from django.core import urlresolvers +from comment_tests.custom_comments.models import CustomComment +from comment_tests.custom_comments.forms import CustomCommentForm + +def get_model(): + return CustomComment + +def get_form(): + return CustomCommentForm + +def get_form_target(): + return urlresolvers.reverse( + "comment_tests.custom_comments.views.custom_submit_comment" + ) + +def get_flag_url(c): + return urlresolvers.reverse( + "comment_tests.custom_comments.views.custom_flag_comment", + args=(c.id,) + ) + +def get_delete_url(c): + return urlresolvers.reverse( + "comment_tests.custom_comments.views.custom_delete_comment", + args=(c.id,) + ) + +def get_approve_url(c): + return urlresolvers.reverse( + "comment_tests.custom_comments.views.custom_approve_comment", + args=(c.id,) + ) diff --git a/tests/regressiontests/comment_tests/custom_comments/forms.py b/tests/comment_tests/custom_comments/forms.py index 07918ddb8c..07918ddb8c 100644 --- a/tests/regressiontests/comment_tests/custom_comments/forms.py +++ b/tests/comment_tests/custom_comments/forms.py diff --git a/tests/regressiontests/comment_tests/custom_comments/models.py b/tests/comment_tests/custom_comments/models.py index 646f6255f2..646f6255f2 100644 --- a/tests/regressiontests/comment_tests/custom_comments/models.py +++ b/tests/comment_tests/custom_comments/models.py diff --git a/tests/regressiontests/comment_tests/custom_comments/views.py b/tests/comment_tests/custom_comments/views.py index 1c3a974367..1c3a974367 100644 --- a/tests/regressiontests/comment_tests/custom_comments/views.py +++ b/tests/comment_tests/custom_comments/views.py diff --git a/tests/regressiontests/comment_tests/fixtures/comment_tests.json b/tests/comment_tests/fixtures/comment_tests.json index 55e2161a4c..55e2161a4c 100644 --- a/tests/regressiontests/comment_tests/fixtures/comment_tests.json +++ b/tests/comment_tests/fixtures/comment_tests.json diff --git a/tests/regressiontests/comment_tests/fixtures/comment_utils.xml b/tests/comment_tests/fixtures/comment_utils.xml index a39bbf63e1..a39bbf63e1 100644 --- a/tests/regressiontests/comment_tests/fixtures/comment_utils.xml +++ b/tests/comment_tests/fixtures/comment_utils.xml diff --git a/tests/regressiontests/comment_tests/models.py b/tests/comment_tests/models.py index 472b66decd..472b66decd 100644 --- a/tests/regressiontests/comment_tests/models.py +++ b/tests/comment_tests/models.py diff --git a/tests/regressiontests/comment_tests/tests/__init__.py b/tests/comment_tests/tests/__init__.py index f80b29e17b..ae4585e187 100644 --- a/tests/regressiontests/comment_tests/tests/__init__.py +++ b/tests/comment_tests/tests/__init__.py @@ -17,7 +17,7 @@ CT = ContentType.objects.get_for_model @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',)) class CommentTestCase(TestCase): fixtures = ["comment_tests"] - urls = 'regressiontests.comment_tests.urls_default' + urls = 'comment_tests.urls_default' def createSomeComments(self): # Two anonymous comments on two different objects @@ -85,11 +85,11 @@ class CommentTestCase(TestCase): d.update(f.initial) return d -from regressiontests.comment_tests.tests.app_api_tests import * -from regressiontests.comment_tests.tests.feed_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 * +from comment_tests.tests.test_app_api import * +from comment_tests.tests.test_feeds import * +from comment_tests.tests.test_models import * +from comment_tests.tests.test_comment_form import * +from comment_tests.tests.test_templatetags import * +from comment_tests.tests.test_comment_view import * +from comment_tests.tests.test_comment_utils_moderators import * +from comment_tests.tests.test_moderation_views import * diff --git a/tests/regressiontests/comment_tests/tests/app_api_tests.py b/tests/comment_tests/tests/test_app_api.py index a756068790..ed23ba39cc 100644 --- a/tests/regressiontests/comment_tests/tests/app_api_tests.py +++ b/tests/comment_tests/tests/test_app_api.py @@ -45,23 +45,23 @@ class CommentAppAPITests(CommentTestCase): @override_settings( - COMMENTS_APP='regressiontests.comment_tests.custom_comments', + COMMENTS_APP='comment_tests.custom_comments', INSTALLED_APPS=list(settings.INSTALLED_APPS) + [ - 'regressiontests.comment_tests.custom_comments'], + 'comment_tests.custom_comments'], ) class CustomCommentTest(CommentTestCase): - urls = 'regressiontests.comment_tests.urls' + urls = 'comment_tests.urls' def testGetCommentApp(self): - from regressiontests.comment_tests import custom_comments + from 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 + from 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 + from comment_tests.custom_comments.forms import CustomCommentForm self.assertEqual(comments.get_form(), CustomCommentForm) def testGetFormTarget(self): diff --git a/tests/regressiontests/comment_tests/tests/comment_form_tests.py b/tests/comment_tests/tests/test_comment_form.py index 39ba57928d..39ba57928d 100644 --- a/tests/regressiontests/comment_tests/tests/comment_form_tests.py +++ b/tests/comment_tests/tests/test_comment_form.py diff --git a/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py b/tests/comment_tests/tests/test_comment_utils_moderators.py index eab87ef53b..6c7a882e25 100644 --- a/tests/regressiontests/comment_tests/tests/comment_utils_moderators_tests.py +++ b/tests/comment_tests/tests/test_comment_utils_moderators.py @@ -4,6 +4,7 @@ from django.contrib.comments.models import Comment from django.contrib.comments.moderation import (moderator, CommentModerator, AlreadyModerated) from django.core import mail +from django.test.utils import override_settings from . import CommentTestCase from ..models import Entry @@ -67,9 +68,10 @@ class CommentUtilsModeratorTests(CommentTestCase): self.assertRaises(AlreadyModerated, moderator.register, Entry, EntryModerator1) def testEmailNotification(self): - moderator.register(Entry, EntryModerator1) - self.createSomeComments() - self.assertEqual(len(mail.outbox), 2) + with override_settings(MANAGERS=("test@example.com",)): + moderator.register(Entry, EntryModerator1) + self.createSomeComments() + self.assertEqual(len(mail.outbox), 2) def testCommentsEnabled(self): moderator.register(Entry, EntryModerator2) @@ -94,4 +96,4 @@ class CommentUtilsModeratorTests(CommentTestCase): def testAutoCloseFieldImmediate(self): moderator.register(Entry, EntryModerator6) c1, c2 = self.createSomeComments() - self.assertEqual(Comment.objects.all().count(), 0)
\ No newline at end of file + self.assertEqual(Comment.objects.all().count(), 0) diff --git a/tests/regressiontests/comment_tests/tests/comment_view_tests.py b/tests/comment_tests/tests/test_comment_view.py index 5c1954026d..0d994d3af8 100644 --- a/tests/regressiontests/comment_tests/tests/comment_view_tests.py +++ b/tests/comment_tests/tests/test_comment_view.py @@ -101,13 +101,43 @@ class CommentViewTests(CommentTestCase): settings.DEBUG = olddebug def testCreateValidComment(self): + address = "1.2.3.4" a = Article.objects.get(pk=1) data = self.getValidData(a) - self.response = self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4") + self.response = self.client.post("/post/", data, REMOTE_ADDR=address) 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.ip_address, address) + self.assertEqual(c.comment, "This is my comment") + + def testCreateValidCommentIPv6(self): + """ + Test creating a valid comment with a long IPv6 address. + Note that this test should fail when Comment.ip_address is an IPAddress instead of a GenericIPAddress, + but does not do so on SQLite or PostgreSQL, because they use the TEXT and INET types, which already + allow storing an IPv6 address internally. + """ + address = "2a02::223:6cff:fe8a:2e8a" + a = Article.objects.get(pk=1) + data = self.getValidData(a) + self.response = self.client.post("/post/", data, REMOTE_ADDR=address) + self.assertEqual(self.response.status_code, 302) + self.assertEqual(Comment.objects.count(), 1) + c = Comment.objects.all()[0] + self.assertEqual(c.ip_address, address) + self.assertEqual(c.comment, "This is my comment") + + def testCreateValidCommentIPv6Unpack(self): + address = "::ffff:18.52.18.52" + a = Article.objects.get(pk=1) + data = self.getValidData(a) + self.response = self.client.post("/post/", data, REMOTE_ADDR=address) + self.assertEqual(self.response.status_code, 302) + self.assertEqual(Comment.objects.count(), 1) + c = Comment.objects.all()[0] + # We trim the '::ffff:' bit off because it is an IPv4 addr + self.assertEqual(c.ip_address, address[7:]) self.assertEqual(c.comment, "This is my comment") def testPostAsAuthenticatedUser(self): diff --git a/tests/regressiontests/comment_tests/tests/feed_tests.py b/tests/comment_tests/tests/test_feeds.py index c93d7abf7d..941ffb6bf2 100644 --- a/tests/regressiontests/comment_tests/tests/feed_tests.py +++ b/tests/comment_tests/tests/test_feeds.py @@ -12,7 +12,7 @@ from ..models import Article class CommentFeedTests(CommentTestCase): - urls = 'regressiontests.comment_tests.urls' + urls = 'comment_tests.urls' feed_url = '/rss/comments/' def setUp(self): diff --git a/tests/regressiontests/comment_tests/tests/model_tests.py b/tests/comment_tests/tests/test_models.py index 69c1a8118f..69c1a8118f 100644 --- a/tests/regressiontests/comment_tests/tests/model_tests.py +++ b/tests/comment_tests/tests/test_models.py diff --git a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/tests/comment_tests/tests/test_moderation_views.py index 0f83d5e210..02af35cfe4 100644 --- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py +++ b/tests/comment_tests/tests/test_moderation_views.py @@ -1,9 +1,10 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals 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 django.utils import translation from . import CommentTestCase @@ -92,6 +93,8 @@ class FlagViewTests(CommentTestCase): self.testFlagPost() self.assertEqual(received_signals, [signals.comment_was_flagged]) + signals.comment_was_flagged.disconnect(receive) + def makeModerator(username): u = User.objects.get(username=username) ct = ContentType.objects.get_for_model(Comment) @@ -163,6 +166,8 @@ class DeleteViewTests(CommentTestCase): self.testDeletePost() self.assertEqual(received_signals, [signals.comment_was_flagged]) + signals.comment_was_flagged.disconnect(receive) + def testDeletedView(self): comments = self.createSomeComments() pk = comments[0].pk @@ -238,6 +243,8 @@ class ApproveViewTests(CommentTestCase): self.testApprovePost() self.assertEqual(received_signals, [signals.comment_was_flagged]) + signals.comment_was_flagged.disconnect(receive) + def testApprovedView(self): comments = self.createSomeComments() pk = comments[0].pk @@ -245,7 +252,7 @@ class ApproveViewTests(CommentTestCase): self.assertTemplateUsed(response, "comments/approved.html") class AdminActionsTests(CommentTestCase): - urls = "regressiontests.comment_tests.urls_admin" + urls = "comment_tests.urls_admin" def setUp(self): super(AdminActionsTests, self).setUp() @@ -281,3 +288,28 @@ class AdminActionsTests(CommentTestCase): response = self.client.get('/admin2/comments/comment/') self.assertEqual(response.status_code, 200) self.assertNotContains(response, '<option value="delete_selected">') + + def performActionAndCheckMessage(self, action, action_params, expected_message): + response = self.client.post('/admin/comments/comment/', + data={'_selected_action': action_params, + 'action': action, + 'index': 0}, + follow=True) + self.assertContains(response, expected_message) + + def testActionsMessageTranslations(self): + c1, c2, c3, c4 = self.createSomeComments() + one_comment = c1.pk + many_comments = [c2.pk, c3.pk, c4.pk] + makeModerator("normaluser") + self.client.login(username="normaluser", password="normaluser") + with translation.override('en'): + #Test approving + self.performActionAndCheckMessage('approve_comments', one_comment, '1 comment was successfully approved') + self.performActionAndCheckMessage('approve_comments', many_comments, '3 comments were successfully approved') + #Test flagging + self.performActionAndCheckMessage('flag_comments', one_comment, '1 comment was successfully flagged') + self.performActionAndCheckMessage('flag_comments', many_comments, '3 comments were successfully flagged') + #Test removing + self.performActionAndCheckMessage('remove_comments', one_comment, '1 comment was successfully removed') + self.performActionAndCheckMessage('remove_comments', many_comments, '3 comments were successfully removed') diff --git a/tests/regressiontests/comment_tests/tests/templatetag_tests.py b/tests/comment_tests/tests/test_templatetags.py index 62b3fb35db..185f6de297 100644 --- a/tests/regressiontests/comment_tests/tests/templatetag_tests.py +++ b/tests/comment_tests/tests/test_templatetags.py @@ -3,11 +3,19 @@ from __future__ import absolute_import 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 django.template import Template, Context, Library, libraries from ..models import Article, Author from . import CommentTestCase +register = Library() + +@register.filter +def noop(variable, param=None): + return variable + +libraries['comment_testtags'] = register + class CommentTemplateTagTests(CommentTestCase): @@ -32,6 +40,9 @@ class CommentTemplateTagTests(CommentTestCase): def testGetCommentFormFromObject(self): self.testGetCommentForm("{% get_comment_form for a as form %}") + def testWhitespaceInGetCommentFormTag(self): + self.testGetCommentForm("{% load comment_testtags %}{% get_comment_form for a|noop:'x y' 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)) @@ -44,6 +55,9 @@ class CommentTemplateTagTests(CommentTestCase): def testRenderCommentFormFromObject(self): self.testRenderCommentForm("{% render_comment_form for a %}") + def testWhitespaceInRenderCommentFormTag(self): + self.testRenderCommentForm("{% load comment_testtags %}{% render_comment_form for a|noop:'x y' %}") + def testRenderCommentFormFromObjectWithQueryCount(self): with self.assertNumQueries(1): self.testRenderCommentFormFromObject() @@ -65,6 +79,10 @@ class CommentTemplateTagTests(CommentTestCase): self.createSomeComments() self.verifyGetCommentCount("{% get_comment_count for a as cc %}") + def testWhitespaceInGetCommentCountTag(self): + self.createSomeComments() + self.verifyGetCommentCount("{% load comment_testtags %}{% get_comment_count for a|noop:'x y' as cc %}") + def verifyGetCommentList(self, tag=None): c1, c2, c3, c4 = Comment.objects.all()[:4] t = "{% load comments %}" + (tag or "{% get_comment_list for comment_tests.author a.id as cl %}") @@ -84,6 +102,10 @@ class CommentTemplateTagTests(CommentTestCase): self.createSomeComments() self.verifyGetCommentList("{% get_comment_list for a as cl %}") + def testWhitespaceInGetCommentListTag(self): + self.createSomeComments() + self.verifyGetCommentList("{% load comment_testtags %}{% get_comment_list for a|noop:'x y' 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 %}" @@ -102,6 +124,15 @@ class CommentTemplateTagTests(CommentTestCase): 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 testWhitespaceInGetCommentPermalinkTag(self): + c1, c2, c3, c4 = self.createSomeComments() + t = "{% load comments comment_testtags %}{% get_comment_list for comment_tests.author author.id as cl %}" + t += "{% get_comment_permalink cl.0|noop:'x y' %}" + 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 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)) @@ -114,6 +145,9 @@ class CommentTemplateTagTests(CommentTestCase): def testRenderCommentListFromObject(self): self.testRenderCommentList("{% render_comment_list for a %}") + def testWhitespaceInRenderCommentListTag(self): + self.testRenderCommentList("{% load comment_testtags %}{% render_comment_list for a|noop:'x y' %}") + def testNumberQueries(self): """ Ensure that the template tags use cached content types to reduce the diff --git a/tests/regressiontests/comment_tests/urls.py b/tests/comment_tests/urls.py index 0a7e8b5fdf..0a7e8b5fdf 100644 --- a/tests/regressiontests/comment_tests/urls.py +++ b/tests/comment_tests/urls.py diff --git a/tests/regressiontests/comment_tests/urls_admin.py b/tests/comment_tests/urls_admin.py index 79a1b75451..79a1b75451 100644 --- a/tests/regressiontests/comment_tests/urls_admin.py +++ b/tests/comment_tests/urls_admin.py diff --git a/tests/regressiontests/comment_tests/urls_default.py b/tests/comment_tests/urls_default.py index e204f9ebcb..e204f9ebcb 100644 --- a/tests/regressiontests/comment_tests/urls_default.py +++ b/tests/comment_tests/urls_default.py diff --git a/tests/regressiontests/conditional_processing/__init__.py b/tests/conditional_processing/__init__.py index 380474e035..380474e035 100644 --- a/tests/regressiontests/conditional_processing/__init__.py +++ b/tests/conditional_processing/__init__.py diff --git a/tests/regressiontests/app_loading/models.py b/tests/conditional_processing/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/app_loading/models.py +++ b/tests/conditional_processing/models.py diff --git a/tests/regressiontests/conditional_processing/models.py b/tests/conditional_processing/tests.py index b47fdf6fb5..77f1ff54ed 100644 --- a/tests/regressiontests/conditional_processing/models.py +++ b/tests/conditional_processing/tests.py @@ -16,7 +16,7 @@ ETAG = 'b4246ffc4f62314ca13147c9d4f76974' EXPIRED_ETAG = '7fae4cd4b0f81e7d2914700043aa8ed6' class ConditionalGet(TestCase): - urls = 'regressiontests.conditional_processing.urls' + urls = 'conditional_processing.urls' def assertFullResponse(self, response, check_last_modified=True, check_etag=True): self.assertEqual(response.status_code, 200) diff --git a/tests/regressiontests/conditional_processing/urls.py b/tests/conditional_processing/urls.py index 1ed7b1264a..1ed7b1264a 100644 --- a/tests/regressiontests/conditional_processing/urls.py +++ b/tests/conditional_processing/urls.py diff --git a/tests/regressiontests/conditional_processing/views.py b/tests/conditional_processing/views.py index 2188c9b462..14a5a83a45 100644 --- a/tests/regressiontests/conditional_processing/views.py +++ b/tests/conditional_processing/views.py @@ -4,7 +4,7 @@ from __future__ import absolute_import from django.views.decorators.http import condition, etag, last_modified from django.http import HttpResponse -from .models import FULL_RESPONSE, LAST_MODIFIED, ETAG +from .tests import FULL_RESPONSE, LAST_MODIFIED, ETAG def index(request): diff --git a/tests/modeltests/model_inheritance_same_model_name/__init__.py b/tests/contenttypes_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/model_inheritance_same_model_name/__init__.py +++ b/tests/contenttypes_tests/__init__.py diff --git a/tests/contenttypes_tests/fixtures/testdata.json b/tests/contenttypes_tests/fixtures/testdata.json new file mode 100644 index 0000000000..52510bfe90 --- /dev/null +++ b/tests/contenttypes_tests/fixtures/testdata.json @@ -0,0 +1,47 @@ +[ + { + "pk": 1, + "model": "contenttypes_tests.author", + "fields": { + "name": "Boris" + } + }, + { + "pk": 1, + "model": "contenttypes_tests.article", + "fields": { + "author": 1, + "title": "Old Article", + "slug": "old_article", + "date_created": "2001-01-01 21:22:23" + } + }, + { + "pk": 2, + "model": "contenttypes_tests.article", + "fields": { + "author": 1, + "title": "Current Article", + "slug": "current_article", + "date_created": "2007-09-17 21:22:23" + } + }, + { + "pk": 3, + "model": "contenttypes_tests.article", + "fields": { + "author": 1, + "title": "Future Article", + "slug": "future_article", + "date_created": "3000-01-01 21:22:23" + } + }, + { + "pk": 1, + "model": "sites.site", + "fields": { + "domain": "testserver", + "name": "testserver" + } + } +] diff --git a/tests/contenttypes_tests/models.py b/tests/contenttypes_tests/models.py new file mode 100644 index 0000000000..3c6685687a --- /dev/null +++ b/tests/contenttypes_tests/models.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import, unicode_literals + +from django.db import models +from django.utils.encoding import python_2_unicode_compatible + +@python_2_unicode_compatible +class Author(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + + def get_absolute_url(self): + return '/views/authors/%s/' % self.id + +@python_2_unicode_compatible +class Article(models.Model): + title = models.CharField(max_length=100) + slug = models.SlugField() + author = models.ForeignKey(Author) + date_created = models.DateTimeField() + + def __str__(self): + return self.title diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py new file mode 100644 index 0000000000..b39e118ec6 --- /dev/null +++ b/tests/contenttypes_tests/tests.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import, unicode_literals + +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from .models import Author, Article + +class ContentTypesViewsTests(TestCase): + fixtures = ['testdata.json'] + urls = 'contenttypes_tests.urls' + + 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 = '/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 = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk) + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_wrong_type_pk(self): + short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects') + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_shortcut_bad_pk(self): + short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242') + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_nonint_content_type(self): + an_author = Author.objects.all()[0] + short_url = '/shortcut/%s/%s/' % ('spam', an_author.pk) + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) + + def test_bad_content_type(self): + an_author = Author.objects.all()[0] + short_url = '/shortcut/%s/%s/' % (42424242, an_author.pk) + response = self.client.get(short_url) + self.assertEqual(response.status_code, 404) diff --git a/tests/contenttypes_tests/urls.py b/tests/contenttypes_tests/urls.py new file mode 100644 index 0000000000..2cfc90b024 --- /dev/null +++ b/tests/contenttypes_tests/urls.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, unicode_literals + +from django.conf.urls import patterns + +urlpatterns = patterns('', + (r'^shortcut/(\d+)/(.*)/$', 'django.contrib.contenttypes.views.shortcut'), +) diff --git a/tests/modeltests/mutually_referential/__init__.py b/tests/context_processors/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/mutually_referential/__init__.py +++ b/tests/context_processors/__init__.py diff --git a/tests/regressiontests/context_processors/models.py b/tests/context_processors/models.py index cde172db68..cde172db68 100644 --- a/tests/regressiontests/context_processors/models.py +++ b/tests/context_processors/models.py diff --git a/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html b/tests/context_processors/templates/context_processors/request_attrs.html index 3978e9d680..3978e9d680 100644 --- a/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html +++ b/tests/context_processors/templates/context_processors/request_attrs.html diff --git a/tests/regressiontests/context_processors/tests.py b/tests/context_processors/tests.py index 1f2209efb0..c57c592de6 100644 --- a/tests/regressiontests/context_processors/tests.py +++ b/tests/context_processors/tests.py @@ -9,7 +9,7 @@ class RequestContextProcessorTests(TestCase): Tests for the ``django.core.context_processors.request`` processor. """ - urls = 'regressiontests.context_processors.urls' + urls = 'context_processors.urls' def test_request_attributes(self): """ diff --git a/tests/regressiontests/context_processors/urls.py b/tests/context_processors/urls.py index 757017c9ca..757017c9ca 100644 --- a/tests/regressiontests/context_processors/urls.py +++ b/tests/context_processors/urls.py diff --git a/tests/regressiontests/context_processors/views.py b/tests/context_processors/views.py index 66e7132c05..66e7132c05 100644 --- a/tests/regressiontests/context_processors/views.py +++ b/tests/context_processors/views.py diff --git a/tests/modeltests/one_to_one/__init__.py b/tests/createsuperuser/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/one_to_one/__init__.py +++ b/tests/createsuperuser/__init__.py diff --git a/tests/regressiontests/builtin_server/models.py b/tests/createsuperuser/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/builtin_server/models.py +++ b/tests/createsuperuser/models.py diff --git a/tests/regressiontests/createsuperuser/tests.py b/tests/createsuperuser/tests.py index 7303b1f2e7..7303b1f2e7 100644 --- a/tests/regressiontests/createsuperuser/tests.py +++ b/tests/createsuperuser/tests.py diff --git a/tests/modeltests/or_lookups/__init__.py b/tests/csrf_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/or_lookups/__init__.py +++ b/tests/csrf_tests/__init__.py diff --git a/tests/regressiontests/csrf_tests/models.py b/tests/csrf_tests/models.py index 71abcc5198..71abcc5198 100644 --- a/tests/regressiontests/csrf_tests/models.py +++ b/tests/csrf_tests/models.py diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index c5b66a31d1..5300b2172a 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -7,6 +7,7 @@ from django.http import HttpRequest, HttpResponse from django.middleware.csrf import CsrfViewMiddleware, CSRF_KEY_LENGTH from django.template import RequestContext, Template from django.test import TestCase +from django.test.utils import override_settings from django.views.decorators.csrf import csrf_exempt, requires_csrf_token, ensure_csrf_cookie @@ -101,7 +102,8 @@ class CsrfViewMiddlewareTest(TestCase): with self.settings(CSRF_COOKIE_NAME='myname', CSRF_COOKIE_DOMAIN='.example.com', CSRF_COOKIE_PATH='/test/', - CSRF_COOKIE_SECURE=True): + CSRF_COOKIE_SECURE=True, + CSRF_COOKIE_HTTPONLY=True): # token_view calls get_token() indirectly CsrfViewMiddleware().process_view(req, token_view, (), {}) resp = token_view(req) @@ -110,6 +112,7 @@ class CsrfViewMiddlewareTest(TestCase): self.assertNotEqual(csrf_cookie, False) self.assertEqual(csrf_cookie['domain'], '.example.com') self.assertEqual(csrf_cookie['secure'], True) + self.assertEqual(csrf_cookie['httponly'], True) self.assertEqual(csrf_cookie['path'], '/test/') self.assertTrue('Cookie' in resp2.get('Vary','')) @@ -267,6 +270,7 @@ class CsrfViewMiddlewareTest(TestCase): csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME] self._check_token_present(resp, csrf_id=csrf_cookie.value) + @override_settings(ALLOWED_HOSTS=['www.example.com']) def test_https_bad_referer(self): """ Test that a POST HTTPS request with a bad referer is rejected @@ -279,6 +283,7 @@ class CsrfViewMiddlewareTest(TestCase): self.assertNotEqual(None, req2) self.assertEqual(403, req2.status_code) + @override_settings(ALLOWED_HOSTS=['www.example.com']) def test_https_good_referer(self): """ Test that a POST HTTPS request with a good referer is accepted @@ -290,6 +295,7 @@ class CsrfViewMiddlewareTest(TestCase): req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertEqual(None, req2) + @override_settings(ALLOWED_HOSTS=['www.example.com']) def test_https_good_referer_2(self): """ Test that a POST HTTPS request with a good referer is accepted diff --git a/tests/modeltests/order_with_respect_to/__init__.py b/tests/custom_columns/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/order_with_respect_to/__init__.py +++ b/tests/custom_columns/__init__.py diff --git a/tests/modeltests/custom_columns/models.py b/tests/custom_columns/models.py index 16f0563d6b..16f0563d6b 100644 --- a/tests/modeltests/custom_columns/models.py +++ b/tests/custom_columns/models.py diff --git a/tests/modeltests/custom_columns/tests.py b/tests/custom_columns/tests.py index a2e5323a75..51028b0c46 100644 --- a/tests/modeltests/custom_columns/tests.py +++ b/tests/custom_columns/tests.py @@ -8,66 +8,71 @@ from .models import Author, Article class CustomColumnsTests(TestCase): - def test_db_column(self): + def setUp(self): a1 = Author.objects.create(first_name="John", last_name="Smith") a2 = Author.objects.create(first_name="Peter", last_name="Jones") art = Article.objects.create(headline="Django lets you build Web apps easily") art.authors = [a1, a2] - # Although the table and column names on Author have been set to custom - # values, nothing about using the Author model has changed... + self.a1 = a1 + self.art = art - # Query the available authors + def test_query_all_available_authors(self): self.assertQuerysetEqual( Author.objects.all(), [ "Peter Jones", "John Smith", ], six.text_type ) + + def test_get_first_name(self): + self.assertEqual( + Author.objects.get(first_name__exact="John"), + self.a1, + ) + + def test_filter_first_name(self): self.assertQuerysetEqual( Author.objects.filter(first_name__exact="John"), [ "John Smith", ], six.text_type - ) - self.assertEqual( - Author.objects.get(first_name__exact="John"), - a1, + ) + def test_field_error(self): self.assertRaises(FieldError, lambda: Author.objects.filter(firstname__exact="John") ) - a = Author.objects.get(last_name__exact="Smith") - a.first_name = "John" - a.last_name = "Smith" + def test_attribute_error(self): + with self.assertRaises(AttributeError): + self.a1.firstname - self.assertRaises(AttributeError, lambda: a.firstname) - self.assertRaises(AttributeError, lambda: a.last) + with self.assertRaises(AttributeError): + self.a1.last - # Although the Article table uses a custom m2m table, - # nothing about using the m2m relationship has changed... - - # Get all the authors for an article + def test_get_all_authors_for_an_article(self): self.assertQuerysetEqual( - art.authors.all(), [ + self.art.authors.all(), [ "Peter Jones", "John Smith", ], six.text_type ) - # Get the articles for an author + + def test_get_all_articles_for_an_author(self): self.assertQuerysetEqual( - a.article_set.all(), [ + self.a1.article_set.all(), [ "Django lets you build Web apps easily", ], lambda a: a.headline ) - # Query the authors across the m2m relation + + def test_get_author_m2m_relation(self): self.assertQuerysetEqual( - art.authors.filter(last_name='Jones'), [ + self.art.authors.filter(last_name='Jones'), [ "Peter Jones" ], six.text_type diff --git a/tests/modeltests/ordering/__init__.py b/tests/custom_columns_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/ordering/__init__.py +++ b/tests/custom_columns_regress/__init__.py diff --git a/tests/regressiontests/custom_columns_regress/models.py b/tests/custom_columns_regress/models.py index 169b2246c3..169b2246c3 100644 --- a/tests/regressiontests/custom_columns_regress/models.py +++ b/tests/custom_columns_regress/models.py diff --git a/tests/regressiontests/custom_columns_regress/tests.py b/tests/custom_columns_regress/tests.py index 7cc66ca2a6..7cc66ca2a6 100644 --- a/tests/regressiontests/custom_columns_regress/tests.py +++ b/tests/custom_columns_regress/tests.py diff --git a/tests/modeltests/prefetch_related/__init__.py b/tests/custom_managers/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/prefetch_related/__init__.py +++ b/tests/custom_managers/__init__.py diff --git a/tests/modeltests/custom_managers/models.py b/tests/custom_managers/models.py index de7c1772ed..2f5e62fc7a 100644 --- a/tests/modeltests/custom_managers/models.py +++ b/tests/custom_managers/models.py @@ -30,11 +30,11 @@ class Person(models.Model): def __str__(self): return "%s %s" % (self.first_name, self.last_name) -# An example of a custom manager that sets get_query_set(). +# An example of a custom manager that sets get_queryset(). class PublishedBookManager(models.Manager): - def get_query_set(self): - return super(PublishedBookManager, self).get_query_set().filter(is_published=True) + def get_queryset(self): + return super(PublishedBookManager, self).get_queryset().filter(is_published=True) @python_2_unicode_compatible class Book(models.Model): @@ -50,8 +50,8 @@ class Book(models.Model): # An example of providing multiple custom managers. class FastCarManager(models.Manager): - def get_query_set(self): - return super(FastCarManager, self).get_query_set().filter(top_speed__gt=150) + def get_queryset(self): + return super(FastCarManager, self).get_queryset().filter(top_speed__gt=150) @python_2_unicode_compatible class Car(models.Model): diff --git a/tests/modeltests/custom_managers/tests.py b/tests/custom_managers/tests.py index 294920de2b..294920de2b 100644 --- a/tests/modeltests/custom_managers/tests.py +++ b/tests/custom_managers/tests.py diff --git a/tests/modeltests/properties/__init__.py b/tests/custom_managers_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/properties/__init__.py +++ b/tests/custom_managers_regress/__init__.py diff --git a/tests/regressiontests/custom_managers_regress/models.py b/tests/custom_managers_regress/models.py index 71073f0fe7..95cf6e8ca1 100644 --- a/tests/regressiontests/custom_managers_regress/models.py +++ b/tests/custom_managers_regress/models.py @@ -10,8 +10,8 @@ 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) + def get_queryset(self): + return super(RestrictedManager, self).get_queryset().filter(is_public=True) @python_2_unicode_compatible class RelatedModel(models.Model): diff --git a/tests/regressiontests/custom_managers_regress/tests.py b/tests/custom_managers_regress/tests.py index 5a9cd91d59..5a9cd91d59 100644 --- a/tests/regressiontests/custom_managers_regress/tests.py +++ b/tests/custom_managers_regress/tests.py diff --git a/tests/modeltests/proxy_model_inheritance/__init__.py b/tests/custom_methods/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/proxy_model_inheritance/__init__.py +++ b/tests/custom_methods/__init__.py diff --git a/tests/modeltests/custom_methods/models.py b/tests/custom_methods/models.py index cef3fd722b..cef3fd722b 100644 --- a/tests/modeltests/custom_methods/models.py +++ b/tests/custom_methods/models.py diff --git a/tests/modeltests/custom_methods/tests.py b/tests/custom_methods/tests.py index 9d7444ba62..9d7444ba62 100644 --- a/tests/modeltests/custom_methods/tests.py +++ b/tests/custom_methods/tests.py diff --git a/tests/modeltests/proxy_model_inheritance/app1/__init__.py b/tests/custom_pk/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/proxy_model_inheritance/app1/__init__.py +++ b/tests/custom_pk/__init__.py diff --git a/tests/modeltests/custom_pk/fields.py b/tests/custom_pk/fields.py index 92096a0ff7..92096a0ff7 100644 --- a/tests/modeltests/custom_pk/fields.py +++ b/tests/custom_pk/fields.py diff --git a/tests/modeltests/custom_pk/models.py b/tests/custom_pk/models.py index 5ef9b69f0c..5ef9b69f0c 100644 --- a/tests/modeltests/custom_pk/models.py +++ b/tests/custom_pk/models.py diff --git a/tests/modeltests/custom_pk/tests.py b/tests/custom_pk/tests.py index 3f562f0bed..3f562f0bed 100644 --- a/tests/modeltests/custom_pk/tests.py +++ b/tests/custom_pk/tests.py diff --git a/tests/modeltests/proxy_model_inheritance/app2/__init__.py b/tests/datatypes/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/proxy_model_inheritance/app2/__init__.py +++ b/tests/datatypes/__init__.py diff --git a/tests/regressiontests/datatypes/models.py b/tests/datatypes/models.py index 8e6027dd0f..8e6027dd0f 100644 --- a/tests/regressiontests/datatypes/models.py +++ b/tests/datatypes/models.py diff --git a/tests/regressiontests/datatypes/tests.py b/tests/datatypes/tests.py index f0ec5f3c0a..f0ec5f3c0a 100644 --- a/tests/regressiontests/datatypes/tests.py +++ b/tests/datatypes/tests.py diff --git a/tests/modeltests/proxy_models/__init__.py b/tests/dates/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/proxy_models/__init__.py +++ b/tests/dates/__init__.py diff --git a/tests/regressiontests/dates/models.py b/tests/dates/models.py index e4bffb7199..23350755e7 100644 --- a/tests/regressiontests/dates/models.py +++ b/tests/dates/models.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db import models from django.utils.encoding import python_2_unicode_compatible diff --git a/tests/regressiontests/dates/tests.py b/tests/dates/tests.py index de28cac436..6c02d597de 100644 --- a/tests/regressiontests/dates/tests.py +++ b/tests/dates/tests.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -from datetime import datetime +import datetime from django.test import TestCase @@ -11,32 +11,32 @@ class DatesTests(TestCase): def test_related_model_traverse(self): a1 = Article.objects.create( title="First one", - pub_date=datetime(2005, 7, 28), + pub_date=datetime.date(2005, 7, 28), ) a2 = Article.objects.create( title="Another one", - pub_date=datetime(2010, 7, 28), + pub_date=datetime.date(2010, 7, 28), ) a3 = Article.objects.create( title="Third one, in the first day", - pub_date=datetime(2005, 7, 28), + pub_date=datetime.date(2005, 7, 28), ) a1.comments.create( text="Im the HULK!", - pub_date=datetime(2005, 7, 28), + pub_date=datetime.date(2005, 7, 28), ) a1.comments.create( text="HULK SMASH!", - pub_date=datetime(2005, 7, 29), + pub_date=datetime.date(2005, 7, 29), ) a2.comments.create( text="LMAO", - pub_date=datetime(2010, 7, 28), + pub_date=datetime.date(2010, 7, 28), ) a3.comments.create( text="+1", - pub_date=datetime(2005, 8, 29), + pub_date=datetime.date(2005, 8, 29), ) c = Category.objects.create(name="serious-news") @@ -44,31 +44,31 @@ class DatesTests(TestCase): self.assertQuerysetEqual( Comment.objects.dates("article__pub_date", "year"), [ - datetime(2005, 1, 1), - datetime(2010, 1, 1), + datetime.date(2005, 1, 1), + datetime.date(2010, 1, 1), ], lambda d: d, ) self.assertQuerysetEqual( Comment.objects.dates("article__pub_date", "month"), [ - datetime(2005, 7, 1), - datetime(2010, 7, 1), + datetime.date(2005, 7, 1), + datetime.date(2010, 7, 1), ], lambda d: d ) self.assertQuerysetEqual( Comment.objects.dates("article__pub_date", "day"), [ - datetime(2005, 7, 28), - datetime(2010, 7, 28), + datetime.date(2005, 7, 28), + datetime.date(2010, 7, 28), ], lambda d: d ) self.assertQuerysetEqual( Article.objects.dates("comments__pub_date", "day"), [ - datetime(2005, 7, 28), - datetime(2005, 7, 29), - datetime(2005, 8, 29), - datetime(2010, 7, 28), + datetime.date(2005, 7, 28), + datetime.date(2005, 7, 29), + datetime.date(2005, 8, 29), + datetime.date(2010, 7, 28), ], lambda d: d ) @@ -77,7 +77,7 @@ class DatesTests(TestCase): ) self.assertQuerysetEqual( Category.objects.dates("articles__pub_date", "day"), [ - datetime(2005, 7, 28), + datetime.date(2005, 7, 28), ], lambda d: d, ) diff --git a/tests/modeltests/raw_query/__init__.py b/tests/datetimes/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/raw_query/__init__.py +++ b/tests/datetimes/__init__.py diff --git a/tests/datetimes/models.py b/tests/datetimes/models.py new file mode 100644 index 0000000000..f21376aa1c --- /dev/null +++ b/tests/datetimes/models.py @@ -0,0 +1,28 @@ +from __future__ import unicode_literals + +from django.db import models +from django.utils.encoding import python_2_unicode_compatible + + +@python_2_unicode_compatible +class Article(models.Model): + title = models.CharField(max_length=100) + pub_date = models.DateTimeField() + + categories = models.ManyToManyField("Category", related_name="articles") + + def __str__(self): + return self.title + +@python_2_unicode_compatible +class Comment(models.Model): + article = models.ForeignKey(Article, related_name="comments") + text = models.TextField() + pub_date = models.DateTimeField() + approval_date = models.DateTimeField(null=True) + + def __str__(self): + return 'Comment to %s (%s)' % (self.article.title, self.pub_date) + +class Category(models.Model): + name = models.CharField(max_length=255) diff --git a/tests/datetimes/tests.py b/tests/datetimes/tests.py new file mode 100644 index 0000000000..58cb060f6b --- /dev/null +++ b/tests/datetimes/tests.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import + +import datetime + +from django.test import TestCase + +from .models import Article, Comment, Category + + +class DateTimesTests(TestCase): + def test_related_model_traverse(self): + a1 = Article.objects.create( + title="First one", + pub_date=datetime.datetime(2005, 7, 28, 9, 0, 0), + ) + a2 = Article.objects.create( + title="Another one", + pub_date=datetime.datetime(2010, 7, 28, 10, 0, 0), + ) + a3 = Article.objects.create( + title="Third one, in the first day", + pub_date=datetime.datetime(2005, 7, 28, 17, 0, 0), + ) + + a1.comments.create( + text="Im the HULK!", + pub_date=datetime.datetime(2005, 7, 28, 9, 30, 0), + ) + a1.comments.create( + text="HULK SMASH!", + pub_date=datetime.datetime(2005, 7, 29, 1, 30, 0), + ) + a2.comments.create( + text="LMAO", + pub_date=datetime.datetime(2010, 7, 28, 10, 10, 10), + ) + a3.comments.create( + text="+1", + pub_date=datetime.datetime(2005, 8, 29, 10, 10, 10), + ) + + c = Category.objects.create(name="serious-news") + c.articles.add(a1, a3) + + self.assertQuerysetEqual( + Comment.objects.datetimes("article__pub_date", "year"), [ + datetime.datetime(2005, 1, 1), + datetime.datetime(2010, 1, 1), + ], + lambda d: d, + ) + self.assertQuerysetEqual( + Comment.objects.datetimes("article__pub_date", "month"), [ + datetime.datetime(2005, 7, 1), + datetime.datetime(2010, 7, 1), + ], + lambda d: d + ) + self.assertQuerysetEqual( + Comment.objects.datetimes("article__pub_date", "day"), [ + datetime.datetime(2005, 7, 28), + datetime.datetime(2010, 7, 28), + ], + lambda d: d + ) + self.assertQuerysetEqual( + Article.objects.datetimes("comments__pub_date", "day"), [ + datetime.datetime(2005, 7, 28), + datetime.datetime(2005, 7, 29), + datetime.datetime(2005, 8, 29), + datetime.datetime(2010, 7, 28), + ], + lambda d: d + ) + self.assertQuerysetEqual( + Article.objects.datetimes("comments__approval_date", "day"), [] + ) + self.assertQuerysetEqual( + Category.objects.datetimes("articles__pub_date", "day"), [ + datetime.datetime(2005, 7, 28), + ], + lambda d: d, + ) diff --git a/tests/modeltests/reserved_names/__init__.py b/tests/db_typecasts/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/reserved_names/__init__.py +++ b/tests/db_typecasts/__init__.py diff --git a/tests/regressiontests/createsuperuser/models.py b/tests/db_typecasts/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/createsuperuser/models.py +++ b/tests/db_typecasts/models.py diff --git a/tests/regressiontests/db_typecasts/tests.py b/tests/db_typecasts/tests.py index 2cf835d94e..2cf835d94e 100644 --- a/tests/regressiontests/db_typecasts/tests.py +++ b/tests/db_typecasts/tests.py diff --git a/tests/modeltests/reverse_lookup/__init__.py b/tests/decorators/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/reverse_lookup/__init__.py +++ b/tests/decorators/__init__.py diff --git a/tests/regressiontests/decorators/models.py b/tests/decorators/models.py index e5a795067b..e5a795067b 100644 --- a/tests/regressiontests/decorators/models.py +++ b/tests/decorators/models.py diff --git a/tests/regressiontests/decorators/tests.py b/tests/decorators/tests.py index 7dc36aa479..8a3f340e9f 100644 --- a/tests/regressiontests/decorators/tests.py +++ b/tests/decorators/tests.py @@ -1,4 +1,3 @@ -import warnings from functools import wraps from django.contrib.admin.views.decorators import staff_member_required @@ -116,22 +115,6 @@ class DecoratorsTest(TestCase): 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" - with warnings.catch_warnings(record=True): - 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") - def test_require_safe_accepts_only_safe_methods(self): """ Test for the require_safe decorator. diff --git a/tests/modeltests/save_delete_hooks/__init__.py b/tests/defaultfilters/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/save_delete_hooks/__init__.py +++ b/tests/defaultfilters/__init__.py diff --git a/tests/regressiontests/db_typecasts/models.py b/tests/defaultfilters/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/db_typecasts/models.py +++ b/tests/defaultfilters/models.py diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/defaultfilters/tests.py index 8596f8c801..21734faf95 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/defaultfilters/tests.py @@ -314,6 +314,11 @@ class DefaultFiltersTests(TestCase): self.assertEqual(urlize('see test[at[example.com'), 'see <a href="http://test[at[example.com" rel="nofollow">test[at[example.com</a>' ) + # Check urlize works with IPv4/IPv6 addresses + self.assertEqual(urlize('http://192.168.0.15/api/9'), + '<a href="http://192.168.0.15/api/9" rel="nofollow">http://192.168.0.15/api/9</a>') + self.assertEqual(urlize('http://[2001:db8:cafe::2]/api/9'), + '<a href="http://[2001:db8:cafe::2]/api/9" rel="nofollow">http://[2001:db8:cafe::2]/api/9</a>') def test_wordcount(self): self.assertEqual(wordcount(''), 0) diff --git a/tests/modeltests/schema/__init__.py b/tests/defer/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/schema/__init__.py +++ b/tests/defer/__init__.py diff --git a/tests/modeltests/defer/models.py b/tests/defer/models.py index 0688cbc984..0688cbc984 100644 --- a/tests/modeltests/defer/models.py +++ b/tests/defer/models.py diff --git a/tests/modeltests/defer/tests.py b/tests/defer/tests.py index 50db5a76b4..50db5a76b4 100644 --- a/tests/modeltests/defer/tests.py +++ b/tests/defer/tests.py diff --git a/tests/modeltests/select_related/__init__.py b/tests/defer_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/select_related/__init__.py +++ b/tests/defer_regress/__init__.py diff --git a/tests/regressiontests/defer_regress/models.py b/tests/defer_regress/models.py index 7528e3b2c9..7528e3b2c9 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/defer_regress/models.py diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/defer_regress/tests.py index d4d722035f..d4d722035f 100644 --- a/tests/regressiontests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py diff --git a/tests/modeltests/serializers/__init__.py b/tests/delete/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/serializers/__init__.py +++ b/tests/delete/__init__.py diff --git a/tests/modeltests/delete/models.py b/tests/delete/models.py index 65d4e6f725..65d4e6f725 100644 --- a/tests/modeltests/delete/models.py +++ b/tests/delete/models.py diff --git a/tests/modeltests/delete/tests.py b/tests/delete/tests.py index 20b815c33d..66173078a0 100644 --- a/tests/modeltests/delete/tests.py +++ b/tests/delete/tests.py @@ -227,7 +227,28 @@ class DeletionTests(TestCase): ) models.signals.post_delete.disconnect(log_post_delete) - models.signals.post_delete.disconnect(log_pre_delete) + models.signals.pre_delete.disconnect(log_pre_delete) + + def test_relational_post_delete_signals_happen_before_parent_object(self): + deletions = [] + + def log_post_delete(instance, **kwargs): + self.assertTrue(R.objects.filter(pk=instance.r_id)) + self.assertIs(type(instance), S) + deletions.append(instance.id) + + r = R.objects.create(pk=1) + S.objects.create(pk=1, r=r) + + models.signals.post_delete.connect(log_post_delete, sender=S) + + try: + r.delete() + finally: + models.signals.post_delete.disconnect(log_post_delete) + + self.assertEqual(len(deletions), 1) + self.assertEqual(deletions[0], 1) @skipUnlessDBFeature("can_defer_constraint_checks") def test_can_defer_constraint_checks(self): diff --git a/tests/modeltests/model_package/__init__.py b/tests/delete_regress/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/modeltests/model_package/__init__.py +++ b/tests/delete_regress/__init__.py diff --git a/tests/regressiontests/delete_regress/models.py b/tests/delete_regress/models.py index dbe383fb41..3632a7dbc1 100644 --- a/tests/regressiontests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -99,3 +99,13 @@ class OrgUnit(models.Model): class Login(models.Model): description = models.CharField(max_length=32) orgunit = models.ForeignKey(OrgUnit) + +class House(models.Model): + address = models.CharField(max_length=32) + +class OrderedPerson(models.Model): + name = models.CharField(max_length=32) + lives_in = models.ForeignKey(House) + + class Meta: + ordering = ['name'] diff --git a/tests/regressiontests/delete_regress/tests.py b/tests/delete_regress/tests.py index c3e4a6a0b7..e88c95e229 100644 --- a/tests/regressiontests/delete_regress/tests.py +++ b/tests/delete_regress/tests.py @@ -10,7 +10,7 @@ from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith, PlayedWithNote, Email, Researcher, Food, Eaten, Policy, Version, Location, Item, Image, File, Photo, FooFile, FooImage, FooPhoto, FooFileProxy, Login, - OrgUnit) + OrgUnit, OrderedPerson, House) # Can't run this test under SQLite, because you can't @@ -22,12 +22,12 @@ class DeleteLockingTest(TransactionTestCase): self.conn2 = new_connections[DEFAULT_DB_ALIAS] # Put both DB connections into managed transaction mode transaction.enter_transaction_management() - transaction.managed(True) - self.conn2._enter_transaction_management(True) + self.conn2.enter_transaction_management() def tearDown(self): # Close down the second connection. transaction.leave_transaction_management() + self.conn2.abort() self.conn2.close() @skipUnlessDBFeature('test_db_allows_multiple_connections') @@ -333,7 +333,7 @@ class Ticket19102Tests(TestCase): ).select_related('orgunit').delete() self.assertFalse(Login.objects.filter(pk=self.l1.pk).exists()) self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists()) - + @skipUnlessDBFeature("update_can_self_select") def test_ticket_19102_defer(self): with self.assertNumQueries(1): @@ -347,3 +347,14 @@ class Ticket19102Tests(TestCase): self.assertFalse(Login.objects.filter(pk=self.l1.pk).exists()) self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists()) + +class OrderedDeleteTests(TestCase): + def test_meta_ordered_delete(self): + # When a subquery is performed by deletion code, the subquery must be + # cleared of all ordering. There was a but that caused _meta ordering + # to be used. Refs #19720. + h = House.objects.create(address='Foo') + OrderedPerson.objects.create(name='Jack', lives_in=h) + OrderedPerson.objects.create(name='Bob', lives_in=h) + OrderedPerson.objects.filter(lives_in__address='Foo').delete() + self.assertEqual(OrderedPerson.objects.count(), 0) diff --git a/tests/modeltests/signals/__init__.py b/tests/deprecation/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/signals/__init__.py +++ b/tests/deprecation/__init__.py diff --git a/tests/regressiontests/defaultfilters/models.py b/tests/deprecation/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/defaultfilters/models.py +++ b/tests/deprecation/models.py diff --git a/tests/deprecation/tests.py b/tests/deprecation/tests.py new file mode 100644 index 0000000000..df752b3149 --- /dev/null +++ b/tests/deprecation/tests.py @@ -0,0 +1,158 @@ +from __future__ import unicode_literals +import warnings + +from django.test.testcases import SimpleTestCase +from django.utils import six +from django.utils.deprecation import RenameMethodsBase + + +class RenameManagerMethods(RenameMethodsBase): + renamed_methods = ( + ('old', 'new', PendingDeprecationWarning), + ) + + +class RenameMethodsTests(SimpleTestCase): + """ + Tests the `RenameMethodsBase` type introduced to rename `get_query_set` + to `get_queryset` across the code base following #15363. + """ + + def test_class_definition_warnings(self): + """ + Ensure a warning is raised upon class definition to suggest renaming + the faulty method. + """ + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter('always') + class Manager(six.with_metaclass(RenameManagerMethods)): + def old(self): + pass + self.assertEqual(len(recorded), 1) + msg = str(recorded[0].message) + self.assertEqual(msg, + '`Manager.old` method should be renamed `new`.') + + def test_get_new_defined(self): + """ + Ensure `old` complains and not `new` when only `new` is defined. + """ + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter('ignore') + class Manager(six.with_metaclass(RenameManagerMethods)): + def new(self): + pass + warnings.simplefilter('always') + manager = Manager() + manager.new() + self.assertEqual(len(recorded), 0) + manager.old() + self.assertEqual(len(recorded), 1) + msg = str(recorded.pop().message) + self.assertEqual(msg, + '`Manager.old` is deprecated, use `new` instead.') + + def test_get_old_defined(self): + """ + Ensure `old` complains when only `old` is defined. + """ + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter('ignore') + class Manager(six.with_metaclass(RenameManagerMethods)): + def old(self): + pass + warnings.simplefilter('always') + manager = Manager() + manager.new() + self.assertEqual(len(recorded), 0) + manager.old() + self.assertEqual(len(recorded), 1) + msg = str(recorded.pop().message) + self.assertEqual(msg, + '`Manager.old` is deprecated, use `new` instead.') + + def test_deprecated_subclass_renamed(self): + """ + Ensure the correct warnings are raised when a class that didn't rename + `old` subclass one that did. + """ + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter('ignore') + class Renamed(six.with_metaclass(RenameManagerMethods)): + def new(self): + pass + class Deprecated(Renamed): + def old(self): + super(Deprecated, self).old() + warnings.simplefilter('always') + deprecated = Deprecated() + deprecated.new() + self.assertEqual(len(recorded), 1) + msg = str(recorded.pop().message) + self.assertEqual(msg, + '`Renamed.old` is deprecated, use `new` instead.') + recorded[:] = [] + deprecated.old() + self.assertEqual(len(recorded), 2) + msgs = [str(warning.message) for warning in recorded] + self.assertEqual(msgs, [ + '`Deprecated.old` is deprecated, use `new` instead.', + '`Renamed.old` is deprecated, use `new` instead.', + ]) + + def test_renamed_subclass_deprecated(self): + """ + Ensure the correct warnings are raised when a class that renamed + `old` subclass one that didn't. + """ + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter('ignore') + class Deprecated(six.with_metaclass(RenameManagerMethods)): + def old(self): + pass + class Renamed(Deprecated): + def new(self): + super(Renamed, self).new() + warnings.simplefilter('always') + renamed = Renamed() + renamed.new() + self.assertEqual(len(recorded), 0) + renamed.old() + self.assertEqual(len(recorded), 1) + msg = str(recorded.pop().message) + self.assertEqual(msg, + '`Renamed.old` is deprecated, use `new` instead.') + + def test_deprecated_subclass_renamed_and_mixins(self): + """ + Ensure the correct warnings are raised when a subclass inherit from a + class that renamed `old` and mixins that may or may not have renamed + `new`. + """ + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter('ignore') + class Renamed(six.with_metaclass(RenameManagerMethods)): + def new(self): + pass + class RenamedMixin(object): + def new(self): + super(RenamedMixin, self).new() + class DeprecatedMixin(object): + def old(self): + super(DeprecatedMixin, self).old() + class Deprecated(DeprecatedMixin, RenamedMixin, Renamed): + pass + warnings.simplefilter('always') + deprecated = Deprecated() + deprecated.new() + self.assertEqual(len(recorded), 1) + msg = str(recorded.pop().message) + self.assertEqual(msg, + '`RenamedMixin.old` is deprecated, use `new` instead.') + deprecated.old() + self.assertEqual(len(recorded), 2) + msgs = [str(warning.message) for warning in recorded] + self.assertEqual(msgs, [ + '`DeprecatedMixin.old` is deprecated, use `new` instead.', + '`RenamedMixin.old` is deprecated, use `new` instead.', + ]) diff --git a/tests/regressiontests/dispatch/__init__.py b/tests/dispatch/__init__.py index 679895bb5c..679895bb5c 100644 --- a/tests/regressiontests/dispatch/__init__.py +++ b/tests/dispatch/__init__.py diff --git a/tests/regressiontests/dispatch/models.py b/tests/dispatch/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/dispatch/models.py +++ b/tests/dispatch/models.py diff --git a/tests/regressiontests/dispatch/tests/__init__.py b/tests/dispatch/tests/__init__.py index b6d26217e1..b6d26217e1 100644 --- a/tests/regressiontests/dispatch/tests/__init__.py +++ b/tests/dispatch/tests/__init__.py diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/dispatch/tests/test_dispatcher.py index 5f8f92acaf..5f8f92acaf 100644 --- a/tests/regressiontests/dispatch/tests/test_dispatcher.py +++ b/tests/dispatch/tests/test_dispatcher.py diff --git a/tests/regressiontests/dispatch/tests/test_saferef.py b/tests/dispatch/tests/test_saferef.py index 30eaddfe18..30eaddfe18 100644 --- a/tests/regressiontests/dispatch/tests/test_saferef.py +++ b/tests/dispatch/tests/test_saferef.py diff --git a/tests/modeltests/select_for_update/__init__.py b/tests/distinct_on_fields/__init__.py index 792d600548..792d600548 100644 --- a/tests/modeltests/select_for_update/__init__.py +++ b/tests/distinct_on_fields/__init__.py diff --git a/tests/modeltests/distinct_on_fields/models.py b/tests/distinct_on_fields/models.py index 7982f435d0..7982f435d0 100644 --- a/tests/modeltests/distinct_on_fields/models.py +++ b/tests/distinct_on_fields/models.py diff --git a/tests/modeltests/distinct_on_fields/tests.py b/tests/distinct_on_fields/tests.py index f62a32e58d..f62a32e58d 100644 --- a/tests/modeltests/distinct_on_fields/tests.py +++ b/tests/distinct_on_fields/tests.py diff --git a/tests/modeltests/str/__init__.py b/tests/empty/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/str/__init__.py +++ b/tests/empty/__init__.py diff --git a/tests/modeltests/empty/models.py b/tests/empty/models.py index a6cdb0aa22..a6cdb0aa22 100644 --- a/tests/modeltests/empty/models.py +++ b/tests/empty/models.py diff --git a/tests/modeltests/tablespaces/__init__.py b/tests/empty/no_models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/tablespaces/__init__.py +++ b/tests/empty/no_models/__init__.py diff --git a/tests/empty/no_models/tests.py b/tests/empty/no_models/tests.py new file mode 100644 index 0000000000..8b8db1af39 --- /dev/null +++ b/tests/empty/no_models/tests.py @@ -0,0 +1,6 @@ +from django.test import TestCase + + +class NoModelTests(TestCase): + """ A placeholder test case. See empty.tests for more info. """ + pass diff --git a/tests/modeltests/empty/tests.py b/tests/empty/tests.py index 4c0c4409d8..6dd9f7c75d 100644 --- a/tests/modeltests/empty/tests.py +++ b/tests/empty/tests.py @@ -31,7 +31,7 @@ class NoModelTests(TestCase): It seemed like an appropriate home for it. """ - @override_settings(INSTALLED_APPS=("modeltests.empty.no_models",)) + @override_settings(INSTALLED_APPS=("empty.no_models",)) def test_no_models(self): with six.assertRaisesRegex(self, ImproperlyConfigured, 'App with label no_models is missing a models.py module.'): diff --git a/tests/modeltests/test_client/__init__.py b/tests/expressions/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/test_client/__init__.py +++ b/tests/expressions/__init__.py diff --git a/tests/modeltests/expressions/models.py b/tests/expressions/models.py index f592a0eb13..f592a0eb13 100644 --- a/tests/modeltests/expressions/models.py +++ b/tests/expressions/models.py diff --git a/tests/modeltests/expressions/tests.py b/tests/expressions/tests.py index a351496442..a351496442 100644 --- a/tests/modeltests/expressions/tests.py +++ b/tests/expressions/tests.py diff --git a/tests/modeltests/timezones/__init__.py b/tests/expressions_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/timezones/__init__.py +++ b/tests/expressions_regress/__init__.py diff --git a/tests/regressiontests/expressions_regress/models.py b/tests/expressions_regress/models.py index 711329031d..711329031d 100644 --- a/tests/regressiontests/expressions_regress/models.py +++ b/tests/expressions_regress/models.py diff --git a/tests/regressiontests/expressions_regress/tests.py b/tests/expressions_regress/tests.py index ddb2c83b4f..ddb2c83b4f 100644 --- a/tests/regressiontests/expressions_regress/tests.py +++ b/tests/expressions_regress/tests.py diff --git a/tests/modeltests/transactions/__init__.py b/tests/extra_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/transactions/__init__.py +++ b/tests/extra_regress/__init__.py diff --git a/tests/regressiontests/extra_regress/models.py b/tests/extra_regress/models.py index 11996438b4..11996438b4 100644 --- a/tests/regressiontests/extra_regress/models.py +++ b/tests/extra_regress/models.py diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/extra_regress/tests.py index 1bc6789edd..194b250c99 100644 --- a/tests/regressiontests/extra_regress/tests.py +++ b/tests/extra_regress/tests.py @@ -166,8 +166,9 @@ class ExtraRegressTests(TestCase): ) self.assertQuerysetEqual( - RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month'), - ['datetime.datetime(2008, 9, 1, 0, 0)'] + RevisionableModel.objects.extra(select={"the_answer": 'id'}).datetimes('when', 'month'), + [datetime.datetime(2008, 9, 1, 0, 0)], + transform=lambda d: d, ) def test_values_with_extra(self): diff --git a/tests/modeltests/update/__init__.py b/tests/field_defaults/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/update/__init__.py +++ b/tests/field_defaults/__init__.py diff --git a/tests/modeltests/field_defaults/models.py b/tests/field_defaults/models.py index c99d4871cd..c99d4871cd 100644 --- a/tests/modeltests/field_defaults/models.py +++ b/tests/field_defaults/models.py diff --git a/tests/modeltests/field_defaults/tests.py b/tests/field_defaults/tests.py index 5d9b45610e..5d9b45610e 100644 --- a/tests/modeltests/field_defaults/tests.py +++ b/tests/field_defaults/tests.py diff --git a/tests/modeltests/update_only_fields/__init__.py b/tests/field_subclassing/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/update_only_fields/__init__.py +++ b/tests/field_subclassing/__init__.py diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/field_subclassing/fields.py index a3867e3671..a3867e3671 100644 --- a/tests/modeltests/field_subclassing/fields.py +++ b/tests/field_subclassing/fields.py diff --git a/tests/modeltests/field_subclassing/models.py b/tests/field_subclassing/models.py index 642573cc83..642573cc83 100644 --- a/tests/modeltests/field_subclassing/models.py +++ b/tests/field_subclassing/models.py diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/field_subclassing/tests.py index 0ec317dea5..9331ff2f3f 100644 --- a/tests/modeltests/field_subclassing/tests.py +++ b/tests/field_subclassing/tests.py @@ -61,7 +61,11 @@ class CustomField(TestCase): # Serialization works, too. stream = serializers.serialize("json", MyModel.objects.all()) - self.assertEqual(stream, '[{"pk": %d, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]' % m1.pk) + self.assertJSONEqual(stream, [{ + "pk": m1.pk, + "model": "field_subclassing.mymodel", + "fields": {"data": "12", "name": "m"} + }]) obj = list(serializers.deserialize("json", stream))[0] self.assertEqual(obj.object, m) diff --git a/tests/regressiontests/bash_completion/__init__.py b/tests/file_storage/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/bash_completion/__init__.py +++ b/tests/file_storage/__init__.py diff --git a/tests/file_storage/magic.png b/tests/file_storage/magic.png Binary files differnew file mode 100644 index 0000000000..a06449f9e9 --- /dev/null +++ b/tests/file_storage/magic.png diff --git a/tests/regressiontests/file_storage/models.py b/tests/file_storage/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/file_storage/models.py +++ b/tests/file_storage/models.py diff --git a/tests/modeltests/model_forms/test.png b/tests/file_storage/test.png Binary files differindex 4f17cd075d..4f17cd075d 100644 --- a/tests/modeltests/model_forms/test.png +++ b/tests/file_storage/test.png diff --git a/tests/regressiontests/file_storage/test1.png b/tests/file_storage/test1.png Binary files differindex bc9874113d..bc9874113d 100644 --- a/tests/regressiontests/file_storage/test1.png +++ b/tests/file_storage/test1.png diff --git a/tests/regressiontests/file_storage/tests.py b/tests/file_storage/tests.py index b6d3e1ff0b..5e6adee894 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -7,6 +7,7 @@ import shutil import sys import tempfile import time +import zlib from datetime import datetime, timedelta from io import BytesIO @@ -26,7 +27,7 @@ from django.utils import six from django.utils import unittest from django.utils._os import upath from django.test.utils import override_settings -from ..servers.tests import LiveServerBase +from servers.tests import LiveServerBase # 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 @@ -54,11 +55,9 @@ class GetStorageClassTests(SimpleTestCase): """ get_storage_class raises an error if the requested import don't exist. """ - self.assertRaisesMessage( - ImproperlyConfigured, - "NonExistingStorage isn't a storage module.", - get_storage_class, - 'NonExistingStorage') + with six.assertRaisesRegex(self, ImproperlyConfigured, + "Error importing module storage: \"No module named '?storage'?\""): + get_storage_class('storage.NonExistingStorage') def test_get_nonexisting_storage_class(self): """ @@ -66,8 +65,8 @@ class GetStorageClassTests(SimpleTestCase): """ self.assertRaisesMessage( ImproperlyConfigured, - 'Storage module "django.core.files.storage" does not define a '\ - '"NonExistingStorage" class.', + 'Module "django.core.files.storage" does not define a ' + '"NonExistingStorage" attribute/class', get_storage_class, 'django.core.files.storage.NonExistingStorage') @@ -76,13 +75,11 @@ class GetStorageClassTests(SimpleTestCase): get_storage_class raises an error if the requested module don't exist. """ # Error message may or may not be the fully qualified path. - six.assertRaisesRegex(self, - 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' - ) + with six.assertRaisesRegex(self, ImproperlyConfigured, + "Error importing module django.core.files.non_existing_storage: " + "\"No module named '?(django.core.files.)?non_existing_storage'?\""): + get_storage_class( + 'django.core.files.non_existing_storage.NonExistingStorage') class FileStorageTests(unittest.TestCase): storage_class = FileSystemStorage @@ -559,6 +556,20 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase): self.assertEqual(image_pil.size, size_1) self.assertEqual(size_1, size_2) + @unittest.skipUnless(Image, "PIL not installed") + def test_bug_19457(self): + """ + Regression test for #19457 + get_image_dimensions fails on some pngs, while Image.size is working good on them + """ + img_path = os.path.join(os.path.dirname(upath(__file__)), "magic.png") + try: + size = get_image_dimensions(img_path) + except zlib.error: + self.fail("Exception raised from get_image_dimensions().") + self.assertEqual(size, Image.open(img_path).size) + + class ContentFileTestCase(unittest.TestCase): def setUp(self): @@ -628,7 +639,7 @@ class FileLikeObjectTestCase(LiveServerBase): f = File(file_like_object) stored_filename = self.storage.save("remote_file.html", f) - stored_file = self.storage.open(stored_filename) remote_file = self.urlopen('/example_view/') - self.assertEqual(stored_file.read(), remote_file.read()) + with self.storage.open(stored_filename) as stored_file: + self.assertEqual(stored_file.read(), remote_file.read()) diff --git a/tests/modeltests/user_commands/__init__.py b/tests/file_uploads/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/user_commands/__init__.py +++ b/tests/file_uploads/__init__.py diff --git a/tests/regressiontests/file_uploads/models.py b/tests/file_uploads/models.py index 3336cc9d90..3336cc9d90 100644 --- a/tests/regressiontests/file_uploads/models.py +++ b/tests/file_uploads/models.py diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/file_uploads/tests.py index 45e7342abd..45e7342abd 100644 --- a/tests/regressiontests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py diff --git a/tests/regressiontests/file_uploads/uploadhandler.py b/tests/file_uploads/uploadhandler.py index b30ef136e9..b30ef136e9 100644 --- a/tests/regressiontests/file_uploads/uploadhandler.py +++ b/tests/file_uploads/uploadhandler.py diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/file_uploads/urls.py index fc5576828f..fc5576828f 100644 --- a/tests/regressiontests/file_uploads/urls.py +++ b/tests/file_uploads/urls.py diff --git a/tests/regressiontests/file_uploads/views.py b/tests/file_uploads/views.py index eb7b654c09..eb7b654c09 100644 --- a/tests/regressiontests/file_uploads/views.py +++ b/tests/file_uploads/views.py diff --git a/tests/regressiontests/bash_completion/management/__init__.py b/tests/files/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/bash_completion/management/__init__.py +++ b/tests/files/__init__.py diff --git a/tests/modeltests/files/models.py b/tests/files/models.py index cefc7c7244..4134472748 100644 --- a/tests/modeltests/files/models.py +++ b/tests/files/models.py @@ -26,5 +26,5 @@ class Storage(models.Model): normal = models.FileField(storage=temp_storage, upload_to='tests') custom = models.FileField(storage=temp_storage, upload_to=custom_upload_to) - random = models.FileField(storage=temp_storage, upload_to=random_upload_to, max_length=16) + random = models.FileField(storage=temp_storage, upload_to=random_upload_to) default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt') diff --git a/tests/modeltests/files/tests.py b/tests/files/tests.py index 71ca103205..cd2d15acdb 100644 --- a/tests/modeltests/files/tests.py +++ b/tests/files/tests.py @@ -5,12 +5,12 @@ import shutil import tempfile from django.core.cache import cache -from django.core.exceptions import ValidationError from django.core.files import File from django.core.files.base import ContentFile from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.utils import unittest +from django.utils.six import StringIO from .models import Storage, temp_storage, temp_storage_location @@ -103,23 +103,31 @@ class FileStorageTests(TestCase): obj4.random.save("random_file", ContentFile("random content")) self.assertTrue(obj4.random.name.endswith("/random_file")) - def test_max_length(self): - """ - Test that FileField validates the length of the generated file name - that will be stored in the database. Regression for #9893. - """ - # upload_to = 'unused', so file names are saved as '456/xxxxx'. - # max_length = 16, so names longer than 12 characters are rejected. - s1 = Storage(random=SimpleUploadedFile(12 * 'x', b"content")) - s1.full_clean() - with self.assertRaises(ValidationError): - Storage(random=SimpleUploadedFile(13 * 'x', b"content")).full_clean() + def test_file_object(self): + # Create sample file + temp_storage.save('tests/example.txt', ContentFile('some content')) + + # Load it as python file object + with open(temp_storage.path('tests/example.txt')) as file_obj: + # Save it using storage and read its content + temp_storage.save('tests/file_obj', file_obj) + self.assertTrue(temp_storage.exists('tests/file_obj')) + with temp_storage.open('tests/file_obj') as f: + self.assertEqual(f.read(), b'some content') + + + def test_stringio(self): + # Test passing StringIO instance as content argument to save + output = StringIO() + output.write('content') + output.seek(0) + + # Save it and read written file + temp_storage.save('tests/stringio', output) + self.assertTrue(temp_storage.exists('tests/stringio')) + with temp_storage.open('tests/stringio') as f: + self.assertEqual(f.read(), b'content') - # Ticket #18515: validation for an already saved file should not check - # against a regenerated file name (and potentially raise a ValidationError - # if max_length is exceeded - s1.save() - s1.full_clean() class FileTests(unittest.TestCase): diff --git a/tests/modeltests/fixtures/__init__.py b/tests/fixtures/__init__.py index 139597f9cb..139597f9cb 100644 --- a/tests/modeltests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py diff --git a/tests/modeltests/fixtures/fixtures/db_fixture_1.default.json b/tests/fixtures/fixtures/db_fixture_1.default.json index 9bb39e400f..9bb39e400f 100644 --- a/tests/modeltests/fixtures/fixtures/db_fixture_1.default.json +++ b/tests/fixtures/fixtures/db_fixture_1.default.json diff --git a/tests/modeltests/fixtures/fixtures/db_fixture_2.default.json.gz b/tests/fixtures/fixtures/db_fixture_2.default.json.gz Binary files differindex 80e4ba139f..80e4ba139f 100644 --- a/tests/modeltests/fixtures/fixtures/db_fixture_2.default.json.gz +++ b/tests/fixtures/fixtures/db_fixture_2.default.json.gz diff --git a/tests/modeltests/fixtures/fixtures/db_fixture_3.nosuchdb.json b/tests/fixtures/fixtures/db_fixture_3.nosuchdb.json index 3da326bce1..3da326bce1 100644 --- a/tests/modeltests/fixtures/fixtures/db_fixture_3.nosuchdb.json +++ b/tests/fixtures/fixtures/db_fixture_3.nosuchdb.json diff --git a/tests/modeltests/fixtures/fixtures/fixture1.json b/tests/fixtures/fixtures/fixture1.json index 332feaef77..332feaef77 100644 --- a/tests/modeltests/fixtures/fixtures/fixture1.json +++ b/tests/fixtures/fixtures/fixture1.json diff --git a/tests/modeltests/fixtures/fixtures/fixture2.json b/tests/fixtures/fixtures/fixture2.json index 01b40d7535..01b40d7535 100644 --- a/tests/modeltests/fixtures/fixtures/fixture2.json +++ b/tests/fixtures/fixtures/fixture2.json diff --git a/tests/modeltests/fixtures/fixtures/fixture2.xml b/tests/fixtures/fixtures/fixture2.xml index 9ced78162e..9ced78162e 100644 --- a/tests/modeltests/fixtures/fixtures/fixture2.xml +++ b/tests/fixtures/fixtures/fixture2.xml diff --git a/tests/modeltests/fixtures/fixtures/fixture3.xml b/tests/fixtures/fixtures/fixture3.xml index 9ced78162e..9ced78162e 100644 --- a/tests/modeltests/fixtures/fixtures/fixture3.xml +++ b/tests/fixtures/fixtures/fixture3.xml diff --git a/tests/modeltests/fixtures/fixtures/fixture4.json.zip b/tests/fixtures/fixtures/fixture4.json.zip Binary files differindex 270cccb3ff..270cccb3ff 100644 --- a/tests/modeltests/fixtures/fixtures/fixture4.json.zip +++ b/tests/fixtures/fixtures/fixture4.json.zip diff --git a/tests/modeltests/fixtures/fixtures/fixture5.json.gz b/tests/fixtures/fixtures/fixture5.json.gz Binary files differindex bb6baca66d..bb6baca66d 100644 --- a/tests/modeltests/fixtures/fixtures/fixture5.json.gz +++ b/tests/fixtures/fixtures/fixture5.json.gz diff --git a/tests/modeltests/fixtures/fixtures/fixture5.json.zip b/tests/fixtures/fixtures/fixture5.json.zip Binary files differindex 9380cef608..9380cef608 100644 --- a/tests/modeltests/fixtures/fixtures/fixture5.json.zip +++ b/tests/fixtures/fixtures/fixture5.json.zip diff --git a/tests/modeltests/fixtures/fixtures/fixture6.json b/tests/fixtures/fixtures/fixture6.json index 60e4733c71..60e4733c71 100644 --- a/tests/modeltests/fixtures/fixtures/fixture6.json +++ b/tests/fixtures/fixtures/fixture6.json diff --git a/tests/modeltests/fixtures/fixtures/fixture7.xml b/tests/fixtures/fixtures/fixture7.xml index 547cba10d2..547cba10d2 100644 --- a/tests/modeltests/fixtures/fixtures/fixture7.xml +++ b/tests/fixtures/fixtures/fixture7.xml diff --git a/tests/modeltests/fixtures/fixtures/fixture8.json b/tests/fixtures/fixtures/fixture8.json index bc113aa00e..bc113aa00e 100644 --- a/tests/modeltests/fixtures/fixtures/fixture8.json +++ b/tests/fixtures/fixtures/fixture8.json diff --git a/tests/modeltests/fixtures/fixtures/fixture9.xml b/tests/fixtures/fixtures/fixture9.xml index 100f63d106..100f63d106 100644 --- a/tests/modeltests/fixtures/fixtures/fixture9.xml +++ b/tests/fixtures/fixtures/fixture9.xml diff --git a/tests/modeltests/fixtures/fixtures/initial_data.json b/tests/fixtures/fixtures/initial_data.json index 8458bb9f0e..8458bb9f0e 100644 --- a/tests/modeltests/fixtures/fixtures/initial_data.json +++ b/tests/fixtures/fixtures/initial_data.json diff --git a/tests/modeltests/fixtures/fixtures/invalid.json b/tests/fixtures/fixtures/invalid.json index fb69f7c949..fb69f7c949 100644 --- a/tests/modeltests/fixtures/fixtures/invalid.json +++ b/tests/fixtures/fixtures/invalid.json diff --git a/tests/modeltests/fixtures/models.py b/tests/fixtures/models.py index 8bd3501926..976716fdc9 100644 --- a/tests/modeltests/fixtures/models.py +++ b/tests/fixtures/models.py @@ -78,8 +78,8 @@ class Person(models.Model): return (self.name,) class SpyManager(PersonManager): - def get_query_set(self): - return super(SpyManager, self).get_query_set().filter(cover_blown=False) + def get_queryset(self): + return super(SpyManager, self).get_queryset().filter(cover_blown=False) class Spy(Person): objects = SpyManager() diff --git a/tests/modeltests/fixtures/tests.py b/tests/fixtures/tests.py index 415ed6dcf2..103612198e 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -22,7 +22,7 @@ class TestCaseFixtureLoadingTests(TestCase): ]) -class FixtureLoadingTests(TestCase): +class DumpDataAssertMixin(object): def _dumpdata_assert(self, args, output, format='json', natural_keys=False, use_base_manager=False, exclude_list=[]): @@ -34,7 +34,15 @@ class FixtureLoadingTests(TestCase): 'use_base_manager': use_base_manager, 'exclude': exclude_list}) command_output = new_io.getvalue().strip() - self.assertEqual(command_output, output) + if format == "json": + self.assertJSONEqual(command_output, output) + elif format == "xml": + self.assertXMLEqual(command_output, output) + else: + self.assertEqual(command_output, output) + + +class FixtureLoadingTests(DumpDataAssertMixin, TestCase): def test_initial_data(self): # syncdb introduces 1 initial data object from initial_data.json. @@ -290,12 +298,7 @@ class FixtureLoadingTests(TestCase): <django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16T12:00:00</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16T13:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="10" model="fixtures.book"><field type="CharField" name="name">Achieving self-awareness of Python programs</field><field to="fixtures.person" name="authors" rel="ManyToManyRel"></field></object></django-objects>""", format='xml', natural_keys=True) -class FixtureTransactionTests(TransactionTestCase): - def _dumpdata_assert(self, args, output, format='json'): - new_io = six.StringIO() - management.call_command('dumpdata', *args, **{'format': format, 'stdout': new_io}) - command_output = new_io.getvalue().strip() - self.assertEqual(command_output, output) +class FixtureTransactionTests(DumpDataAssertMixin, TransactionTestCase): @skipUnlessDBFeature('supports_forward_references') def test_format_discovery(self): diff --git a/tests/modeltests/fixtures_model_package/__init__.py b/tests/fixtures_model_package/__init__.py index 139597f9cb..139597f9cb 100644 --- a/tests/modeltests/fixtures_model_package/__init__.py +++ b/tests/fixtures_model_package/__init__.py diff --git a/tests/modeltests/fixtures_model_package/fixtures/fixture1.json b/tests/fixtures_model_package/fixtures/fixture1.json index 7684d84609..7684d84609 100644 --- a/tests/modeltests/fixtures_model_package/fixtures/fixture1.json +++ b/tests/fixtures_model_package/fixtures/fixture1.json diff --git a/tests/modeltests/fixtures_model_package/fixtures/fixture2.json b/tests/fixtures_model_package/fixtures/fixture2.json index 4997627385..4997627385 100644 --- a/tests/modeltests/fixtures_model_package/fixtures/fixture2.json +++ b/tests/fixtures_model_package/fixtures/fixture2.json diff --git a/tests/modeltests/fixtures_model_package/fixtures/fixture2.xml b/tests/fixtures_model_package/fixtures/fixture2.xml index 55337cf810..55337cf810 100644 --- a/tests/modeltests/fixtures_model_package/fixtures/fixture2.xml +++ b/tests/fixtures_model_package/fixtures/fixture2.xml diff --git a/tests/modeltests/fixtures_model_package/fixtures/initial_data.json b/tests/fixtures_model_package/fixtures/initial_data.json index 1d1cf15e03..1d1cf15e03 100644 --- a/tests/modeltests/fixtures_model_package/fixtures/initial_data.json +++ b/tests/fixtures_model_package/fixtures/initial_data.json diff --git a/tests/modeltests/fixtures_model_package/models/__init__.py b/tests/fixtures_model_package/models/__init__.py index deeba48aa9..deeba48aa9 100644 --- a/tests/modeltests/fixtures_model_package/models/__init__.py +++ b/tests/fixtures_model_package/models/__init__.py diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py index d147fe68a7..c250f647ce 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/fixtures_model_package/tests.py @@ -25,7 +25,8 @@ class SampleTestCase(TestCase): class TestNoInitialDataLoading(TransactionTestCase): def test_syncdb(self): - with transaction.commit_manually(): + transaction.set_autocommit(False) + try: Book.objects.all().delete() management.call_command( @@ -35,6 +36,9 @@ class TestNoInitialDataLoading(TransactionTestCase): ) self.assertQuerysetEqual(Book.objects.all(), []) transaction.rollback() + finally: + transaction.set_autocommit(True) + def test_flush(self): # Test presence of fixture (flush called by TransactionTestCase) @@ -45,7 +49,8 @@ class TestNoInitialDataLoading(TransactionTestCase): lambda a: a.name ) - with transaction.commit_manually(): + transaction.set_autocommit(False) + try: management.call_command( 'flush', verbosity=0, @@ -55,6 +60,8 @@ class TestNoInitialDataLoading(TransactionTestCase): ) self.assertQuerysetEqual(Book.objects.all(), []) transaction.rollback() + finally: + transaction.set_autocommit(True) class FixtureTestCase(TestCase): diff --git a/tests/modeltests/user_commands/management/__init__.py b/tests/fixtures_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/user_commands/management/__init__.py +++ b/tests/fixtures_regress/__init__.py diff --git a/tests/regressiontests/fixtures_regress/fixtures/absolute.json b/tests/fixtures_regress/fixtures/absolute.json index 37ed3f6886..37ed3f6886 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/absolute.json +++ b/tests/fixtures_regress/fixtures/absolute.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/animal.xml b/tests/fixtures_regress/fixtures/animal.xml index 0383c60fc1..0383c60fc1 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/animal.xml +++ b/tests/fixtures_regress/fixtures/animal.xml diff --git a/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn b/tests/fixtures_regress/fixtures/bad_fixture1.unkn index a8b0a0c56c..a8b0a0c56c 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn +++ b/tests/fixtures_regress/fixtures/bad_fixture1.unkn diff --git a/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml b/tests/fixtures_regress/fixtures/bad_fixture2.xml index 87b809fbc6..87b809fbc6 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml +++ b/tests/fixtures_regress/fixtures/bad_fixture2.xml diff --git a/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json b/tests/fixtures_regress/fixtures/big-fixture.json index e655fbbf3f..e655fbbf3f 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json +++ b/tests/fixtures_regress/fixtures/big-fixture.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/empty.json b/tests/fixtures_regress/fixtures/empty.json index 0637a088a0..0637a088a0 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/empty.json +++ b/tests/fixtures_regress/fixtures/empty.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref.json b/tests/fixtures_regress/fixtures/forward_ref.json index 237b076243..237b076243 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/forward_ref.json +++ b/tests/fixtures_regress/fixtures/forward_ref.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json b/tests/fixtures_regress/fixtures/forward_ref_bad_data.json index 3a3fb64360..3a3fb64360 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json +++ b/tests/fixtures_regress/fixtures/forward_ref_bad_data.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json b/tests/fixtures_regress/fixtures/forward_ref_lookup.json index 42e8ec0877..42e8ec0877 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json +++ b/tests/fixtures_regress/fixtures/forward_ref_lookup.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json b/tests/fixtures_regress/fixtures/model-inheritance.json index 00c482b3dd..00c482b3dd 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json +++ b/tests/fixtures_regress/fixtures/model-inheritance.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json b/tests/fixtures_regress/fixtures/nk-inheritance.json index 08e5d4feee..08e5d4feee 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance.json +++ b/tests/fixtures_regress/fixtures/nk-inheritance.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml b/tests/fixtures_regress/fixtures/nk-inheritance2.xml index 7eb17a6b7e..7eb17a6b7e 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/nk-inheritance2.xml +++ b/tests/fixtures_regress/fixtures/nk-inheritance2.xml diff --git a/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json b/tests/fixtures_regress/fixtures/non_natural_1.json index 4bce792e35..4bce792e35 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json +++ b/tests/fixtures_regress/fixtures/non_natural_1.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml b/tests/fixtures_regress/fixtures/non_natural_2.xml index 280ad3758b..280ad3758b 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml +++ b/tests/fixtures_regress/fixtures/non_natural_2.xml diff --git a/tests/regressiontests/fixtures_regress/fixtures/pretty.xml b/tests/fixtures_regress/fixtures/pretty.xml index 68e5710c6a..68e5710c6a 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/pretty.xml +++ b/tests/fixtures_regress/fixtures/pretty.xml diff --git a/tests/regressiontests/fixtures_regress/fixtures/sequence.json b/tests/fixtures_regress/fixtures/sequence.json index 60bfcdd547..60bfcdd547 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/sequence.json +++ b/tests/fixtures_regress/fixtures/sequence.json diff --git a/tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json b/tests/fixtures_regress/fixtures/sequence_extra.json index 03c0f36696..03c0f36696 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/sequence_extra.json +++ b/tests/fixtures_regress/fixtures/sequence_extra.json diff --git a/tests/fixtures_regress/fixtures/sequence_extra_xml.xml b/tests/fixtures_regress/fixtures/sequence_extra_xml.xml new file mode 100644 index 0000000000..dd2ee7c28f --- /dev/null +++ b/tests/fixtures_regress/fixtures/sequence_extra_xml.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <object pk="1" model="fixtures_regress.animal"> + <field type="CharField" name="name">Wolf</field> + <field type="CharField" name="extra_name">Super Wolf</field> + <field type="CharField" name="latin_name">Canis lupus</field> + <field type="IntegerField" name="count">3</field> + <field type="FloatField" name="weight">1.2</field> + </object> +</django-objects> diff --git a/tests/regressiontests/fixtures_regress/fixtures/thingy.json b/tests/fixtures_regress/fixtures/thingy.json index 1693177b98..1693177b98 100644 --- a/tests/regressiontests/fixtures_regress/fixtures/thingy.json +++ b/tests/fixtures_regress/fixtures/thingy.json diff --git a/tests/regressiontests/fixtures_regress/fixtures_1/forward_ref_1.json b/tests/fixtures_regress/fixtures_1/forward_ref_1.json index 1a75037b48..1a75037b48 100644 --- a/tests/regressiontests/fixtures_regress/fixtures_1/forward_ref_1.json +++ b/tests/fixtures_regress/fixtures_1/forward_ref_1.json diff --git a/tests/regressiontests/fixtures_regress/fixtures_2/forward_ref_2.json b/tests/fixtures_regress/fixtures_2/forward_ref_2.json index 9cb63085a4..9cb63085a4 100644 --- a/tests/regressiontests/fixtures_regress/fixtures_2/forward_ref_2.json +++ b/tests/fixtures_regress/fixtures_2/forward_ref_2.json diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/fixtures_regress/models.py index 1cc30d2e51..1cc30d2e51 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/fixtures_regress/models.py diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index 988c5acd0c..02e923e386 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -18,6 +18,7 @@ from django.utils.encoding import force_text from django.utils._os import upath from django.utils import six from django.utils.six import PY3, StringIO +import json from .models import (Animal, Stuff, Absolute, Parent, Child, Article, Widget, Store, Person, Book, NKChild, RefToNKChild, Circle1, Circle2, Circle3, @@ -86,6 +87,20 @@ class TestFixtures(TestCase): ) self.assertEqual(Animal.specimens.all()[0].name, 'Lion') + def test_loaddata_not_found_fields_ignore_xml(self): + """ + Test for ticket #19998 -- Ignore entries in the XML serialised data + for fields that have been removed from the model definition. + """ + management.call_command( + 'loaddata', + 'sequence_extra_xml', + ignore=True, + verbosity=0, + commit=False + ) + self.assertEqual(Animal.specimens.all()[0].name, 'Wolf') + @skipIfDBFeature('interprets_empty_strings_as_nulls') def test_pretty_print_xml(self): """ @@ -334,15 +349,17 @@ class TestFixtures(TestCase): # between different Python versions. data = re.sub('0{6,}\d', '', data) - 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.2, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}' - platypus_json = platypus_json % animal.pk + animals_data = sorted([ + {"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}, + {"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}, + {"pk": animal.pk, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}, + ], key=lambda x: x["pk"]) + + data = sorted(json.loads(data), key=lambda x: x["pk"]) + + self.maxDiff = 1024 + self.assertEqual(data, animals_data) - 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): """ @@ -358,7 +375,7 @@ class TestFixtures(TestCase): format='json', stdout=stdout ) - self.assertEqual( + self.assertJSONEqual( stdout.getvalue(), """[{"pk": %d, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]""" % widget.pk @@ -519,7 +536,7 @@ class NaturalKeyFixtureTests(TestCase): use_natural_keys=True, stdout=stdout, ) - self.assertEqual( + self.assertJSONEqual( stdout.getvalue(), """[{"pk": 2, "model": "fixtures_regress.store", "fields": {"main": null, "name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"main": null, "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"]}}]""" ) @@ -681,5 +698,8 @@ class TestTicket11101(TransactionTestCase): @skipUnlessDBFeature('supports_transactions') def test_ticket_11101(self): """Test that fixtures can be rolled back (ticket #11101).""" - ticket_11101 = transaction.commit_manually(self.ticket_11101) - ticket_11101() + transaction.set_autocommit(False) + try: + self.ticket_11101() + finally: + transaction.set_autocommit(True) diff --git a/tests/modeltests/user_commands/management/commands/__init__.py b/tests/force_insert_update/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/user_commands/management/commands/__init__.py +++ b/tests/force_insert_update/__init__.py diff --git a/tests/modeltests/force_insert_update/models.py b/tests/force_insert_update/models.py index 56d6624e0d..56d6624e0d 100644 --- a/tests/modeltests/force_insert_update/models.py +++ b/tests/force_insert_update/models.py diff --git a/tests/modeltests/force_insert_update/tests.py b/tests/force_insert_update/tests.py index a5b2dcebb5..a5b2dcebb5 100644 --- a/tests/modeltests/force_insert_update/tests.py +++ b/tests/force_insert_update/tests.py diff --git a/tests/modeltests/validators/__init__.py b/tests/foreign_object/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/modeltests/validators/__init__.py +++ b/tests/foreign_object/__init__.py diff --git a/tests/foreign_object/models.py b/tests/foreign_object/models.py new file mode 100644 index 0000000000..2d02b5624c --- /dev/null +++ b/tests/foreign_object/models.py @@ -0,0 +1,152 @@ +import datetime + +from django.db import models +from django.db.models.fields.related import ReverseSingleRelatedObjectDescriptor +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import get_language + +class Country(models.Model): + # Table Column Fields + name = models.CharField(max_length=50) + + def __unicode__(self): + return self.name + + +class Person(models.Model): + # Table Column Fields + name = models.CharField(max_length=128) + person_country_id = models.IntegerField() + + # Relation Fields + person_country = models.ForeignObject( + Country, from_fields=['person_country_id'], to_fields=['id']) + friends = models.ManyToManyField('self', through='Friendship', symmetrical=False) + + class Meta: + ordering = ('name',) + + def __unicode__(self): + return self.name + +class Group(models.Model): + # Table Column Fields + name = models.CharField(max_length=128) + group_country = models.ForeignKey(Country) + members = models.ManyToManyField(Person, related_name='groups', through='Membership') + + class Meta: + ordering = ('name',) + + def __unicode__(self): + return self.name + + +class Membership(models.Model): + # Table Column Fields + membership_country = models.ForeignKey(Country) + date_joined = models.DateTimeField(default=datetime.datetime.now) + invite_reason = models.CharField(max_length=64, null=True) + person_id = models.IntegerField() + group_id = models.IntegerField() + + # Relation Fields + person = models.ForeignObject(Person, + from_fields=['membership_country', 'person_id'], + to_fields=['person_country_id', 'id']) + group = models.ForeignObject(Group, + from_fields=['membership_country', 'group_id'], + to_fields=['group_country', 'id']) + + class Meta: + ordering = ('date_joined', 'invite_reason') + + def __unicode__(self): + return "%s is a member of %s" % (self.person.name, self.group.name) + + +class Friendship(models.Model): + # Table Column Fields + from_friend_country = models.ForeignKey(Country, related_name="from_friend_country") + from_friend_id = models.IntegerField() + to_friend_country_id = models.IntegerField() + to_friend_id = models.IntegerField() + + # Relation Fields + from_friend = models.ForeignObject(Person, + from_fields=['from_friend_country', 'from_friend_id'], + to_fields=['person_country_id', 'id'], + related_name='from_friend') + + to_friend_country = models.ForeignObject(Country, + from_fields=['to_friend_country_id'], + to_fields=['id'], + related_name='to_friend_country') + + to_friend = models.ForeignObject(Person, + from_fields=['to_friend_country_id', 'to_friend_id'], + to_fields=['person_country_id', 'id'], + related_name='to_friend') + +class ArticleTranslationDescriptor(ReverseSingleRelatedObjectDescriptor): + """ + The set of articletranslation should not set any local fields. + """ + def __set__(self, instance, value): + if instance is None: + raise AttributeError("%s must be accessed via instance" % self.field.name) + setattr(instance, self.cache_name, value) + if value is not None and not self.field.rel.multiple: + setattr(value, self.field.related.get_cache_name(), instance) + +class ColConstraint(object): + # Antyhing with as_sql() method works in get_extra_restriction(). + def __init__(self, alias, col, value): + self.alias, self.col, self.value = alias, col, value + + def as_sql(self, qn, connection): + return '%s.%s = %%s' % (qn(self.alias), qn(self.col)), [self.value] + +class ActiveTranslationField(models.ForeignObject): + """ + This field will allow querying and fetching the currently active translation + for Article from ArticleTranslation. + """ + requires_unique_target = False + + def get_extra_restriction(self, where_class, alias, related_alias): + return ColConstraint(alias, 'lang', get_language()) + + def get_extra_descriptor_filter(self): + return {'lang': get_language()} + + def contribute_to_class(self, cls, name): + super(ActiveTranslationField, self).contribute_to_class(cls, name) + setattr(cls, self.name, ArticleTranslationDescriptor(self)) + +@python_2_unicode_compatible +class Article(models.Model): + active_translation = ActiveTranslationField( + 'ArticleTranslation', + from_fields=['id'], + to_fields=['article'], + related_name='+', + null=True) + pub_date = models.DateField() + + def __str__(self): + try: + return self.active_translation.title + except ArticleTranslation.DoesNotExist: + return '[No translation found]' + +class ArticleTranslation(models.Model): + article = models.ForeignKey(Article) + lang = models.CharField(max_length='2') + title = models.CharField(max_length=100) + body = models.TextField() + abstract = models.CharField(max_length=400, null=True) + + class Meta: + unique_together = ('article', 'lang') + ordering = ('active_translation__title',) diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py new file mode 100644 index 0000000000..2ca13cb786 --- /dev/null +++ b/tests/foreign_object/tests.py @@ -0,0 +1,338 @@ +import datetime +from operator import attrgetter + +from .models import Country, Person, Group, Membership, Friendship, Article, ArticleTranslation +from django.test import TestCase +from django.utils.translation import activate +from django import forms + +class MultiColumnFKTests(TestCase): + def setUp(self): + # Creating countries + self.usa = Country.objects.create(name="United States of America") + self.soviet_union = Country.objects.create(name="Soviet Union") + Person() + # Creating People + self.bob = Person() + self.bob.name = 'Bob' + self.bob.person_country = self.usa + self.bob.save() + self.jim = Person.objects.create(name='Jim', person_country=self.usa) + self.george = Person.objects.create(name='George', person_country=self.usa) + + self.jane = Person.objects.create(name='Jane', person_country=self.soviet_union) + self.mark = Person.objects.create(name='Mark', person_country=self.soviet_union) + self.sam = Person.objects.create(name='Sam', person_country=self.soviet_union) + + # Creating Groups + self.kgb = Group.objects.create(name='KGB', group_country=self.soviet_union) + self.cia = Group.objects.create(name='CIA', group_country=self.usa) + self.republican = Group.objects.create(name='Republican', group_country=self.usa) + self.democrat = Group.objects.create(name='Democrat', group_country=self.usa) + + def test_get_succeeds_on_multicolumn_match(self): + # Membership objects have access to their related Person if both + # country_ids match between them + membership = Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.bob.id, group_id=self.cia.id) + + person = membership.person + self.assertEqual((person.id, person.name), (self.bob.id, "Bob")) + + def test_get_fails_on_multicolumn_mismatch(self): + # Membership objects returns DoesNotExist error when the there is no + # Person with the same id and country_id + membership = Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.jane.id, group_id=self.cia.id) + + self.assertRaises(Person.DoesNotExist, getattr, membership, 'person') + + def test_reverse_query_returns_correct_result(self): + # Creating a valid membership because it has the same country has the person + Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.bob.id, group_id=self.cia.id) + + # Creating an invalid membership because it has a different country has the person + Membership.objects.create( + membership_country_id=self.soviet_union.id, person_id=self.bob.id, + group_id=self.republican.id) + + self.assertQuerysetEqual( + self.bob.membership_set.all(), [ + self.cia.id + ], + attrgetter("group_id") + ) + + def test_query_filters_correctly(self): + + # Creating a to valid memberships + Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.bob.id, group_id=self.cia.id) + Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.jim.id, + group_id=self.cia.id) + + # Creating an invalid membership + Membership.objects.create(membership_country_id=self.soviet_union.id, + person_id=self.george.id, group_id=self.cia.id) + + self.assertQuerysetEqual( + Membership.objects.filter(person__name__contains='o'), [ + self.bob.id + ], + attrgetter("person_id") + ) + + def test_reverse_query_filters_correctly(self): + + timemark = datetime.datetime.utcnow() + timedelta = datetime.timedelta(days=1) + + # Creating a to valid memberships + Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.bob.id, + group_id=self.cia.id, date_joined=timemark - timedelta) + Membership.objects.create( + membership_country_id=self.usa.id, person_id=self.jim.id, + group_id=self.cia.id, date_joined=timemark + timedelta) + + # Creating an invalid membership + Membership.objects.create( + membership_country_id=self.soviet_union.id, person_id=self.george.id, + group_id=self.cia.id, date_joined=timemark + timedelta) + + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gte=timemark), [ + 'Jim' + ], + attrgetter('name') + ) + + def test_forward_in_lookup_filters_correctly(self): + Membership.objects.create(membership_country_id=self.usa.id, person_id=self.bob.id, + group_id=self.cia.id) + Membership.objects.create(membership_country_id=self.usa.id, person_id=self.jim.id, + group_id=self.cia.id) + + # Creating an invalid membership + Membership.objects.create( + membership_country_id=self.soviet_union.id, person_id=self.george.id, + group_id=self.cia.id) + + self.assertQuerysetEqual( + Membership.objects.filter(person__in=[self.george, self.jim]), [ + self.jim.id, + ], + attrgetter('person_id') + ) + + self.assertQuerysetEqual( + Membership.objects.filter(person__in=Person.objects.filter(name='Jim')), [ + self.jim.id, + ], + attrgetter('person_id') + ) + + def test_select_related_foreignkey_forward_works(self): + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat) + + with self.assertNumQueries(1): + people = [m.person for m in Membership.objects.select_related('person')] + + normal_people = [m.person for m in Membership.objects.all()] + self.assertEqual(people, normal_people) + + def test_prefetch_foreignkey_forward_works(self): + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat) + + with self.assertNumQueries(2): + people = [m.person for m in Membership.objects.prefetch_related('person')] + + normal_people = [m.person for m in Membership.objects.all()] + self.assertEqual(people, normal_people) + + def test_prefetch_foreignkey_reverse_works(self): + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat) + with self.assertNumQueries(2): + membership_sets = [list(p.membership_set.all()) + for p in Person.objects.prefetch_related('membership_set')] + + normal_membership_sets = [list(p.membership_set.all()) for p in Person.objects.all()] + self.assertEqual(membership_sets, normal_membership_sets) + + def test_m2m_through_forward_returns_valid_members(self): + # We start out by making sure that the Group 'CIA' has no members. + self.assertQuerysetEqual( + self.cia.members.all(), + [] + ) + + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.cia) + + # Let's check to make sure that it worked. Bob and Jim should be members of the CIA. + + self.assertQuerysetEqual( + self.cia.members.all(), [ + 'Bob', + 'Jim' + ], attrgetter("name") + ) + + def test_m2m_through_reverse_returns_valid_members(self): + # We start out by making sure that Bob is in no groups. + self.assertQuerysetEqual( + self.bob.groups.all(), + [] + ) + + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.bob, + group=self.republican) + + # Bob should be in the CIA and a Republican + self.assertQuerysetEqual( + self.bob.groups.all(), [ + 'CIA', + 'Republican' + ], attrgetter("name") + ) + + def test_m2m_through_forward_ignores_invalid_members(self): + # We start out by making sure that the Group 'CIA' has no members. + self.assertQuerysetEqual( + self.cia.members.all(), + [] + ) + + # Something adds jane to group CIA but Jane is in Soviet Union which isn't CIA's country + Membership.objects.create(membership_country=self.usa, person=self.jane, group=self.cia) + + # There should still be no members in CIA + self.assertQuerysetEqual( + self.cia.members.all(), + [] + ) + + def test_m2m_through_reverse_ignores_invalid_members(self): + # We start out by making sure that Jane has no groups. + self.assertQuerysetEqual( + self.jane.groups.all(), + [] + ) + + # Something adds jane to group CIA but Jane is in Soviet Union which isn't CIA's country + Membership.objects.create(membership_country=self.usa, person=self.jane, group=self.cia) + + # Jane should still not be in any groups + self.assertQuerysetEqual( + self.jane.groups.all(), + [] + ) + + def test_m2m_through_on_self_works(self): + self.assertQuerysetEqual( + self.jane.friends.all(), + [] + ) + + Friendship.objects.create( + from_friend_country=self.jane.person_country, from_friend=self.jane, + to_friend_country=self.george.person_country, to_friend=self.george) + + self.assertQuerysetEqual( + self.jane.friends.all(), + ['George'], attrgetter("name") + ) + + def test_m2m_through_on_self_ignores_mismatch_columns(self): + self.assertQuerysetEqual(self.jane.friends.all(), []) + + # Note that we use ids instead of instances. This is because instances on ForeignObject + # properties will set all related field off of the given instance + Friendship.objects.create( + from_friend_id=self.jane.id, to_friend_id=self.george.id, + to_friend_country_id=self.jane.person_country_id, + from_friend_country_id=self.george.person_country_id) + + self.assertQuerysetEqual(self.jane.friends.all(), []) + + def test_prefetch_related_m2m_foward_works(self): + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat) + + with self.assertNumQueries(2): + members_lists = [list(g.members.all()) + for g in Group.objects.prefetch_related('members')] + + normal_members_lists = [list(g.members.all()) for g in Group.objects.all()] + self.assertEqual(members_lists, normal_members_lists) + + def test_prefetch_related_m2m_reverse_works(self): + Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia) + Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat) + + with self.assertNumQueries(2): + groups_lists = [list(p.groups.all()) for p in Person.objects.prefetch_related('groups')] + + normal_groups_lists = [list(p.groups.all()) for p in Person.objects.all()] + self.assertEqual(groups_lists, normal_groups_lists) + + def test_translations(self): + activate('fi') + a1 = Article.objects.create(pub_date=datetime.date.today()) + at1_fi = ArticleTranslation(article=a1, lang='fi', title='Otsikko', body='Diipadaapa') + at1_fi.save() + at2_en = ArticleTranslation(article=a1, lang='en', title='Title', body='Lalalalala') + at2_en.save() + with self.assertNumQueries(1): + fetched = Article.objects.select_related('active_translation').get( + active_translation__title='Otsikko') + self.assertTrue(fetched.active_translation.title == 'Otsikko') + a2 = Article.objects.create(pub_date=datetime.date.today()) + at2_fi = ArticleTranslation(article=a2, lang='fi', title='Atsikko', body='Diipadaapa', + abstract='dipad') + at2_fi.save() + a3 = Article.objects.create(pub_date=datetime.date.today()) + at3_en = ArticleTranslation(article=a3, lang='en', title='A title', body='lalalalala', + abstract='lala') + at3_en.save() + # Test model initialization with active_translation field. + a3 = Article(id=a3.id, pub_date=a3.pub_date, active_translation=at3_en) + a3.save() + self.assertEqual( + list(Article.objects.filter(active_translation__abstract=None)), + [a1, a3]) + self.assertEqual( + list(Article.objects.filter(active_translation__abstract=None, + active_translation__pk__isnull=False)), + [a1]) + activate('en') + self.assertEqual( + list(Article.objects.filter(active_translation__abstract=None)), + [a1, a2]) + +class FormsTests(TestCase): + # ForeignObjects should not have any form fields, currently the user needs + # to manually deal with the foreignobject relation. + class ArticleForm(forms.ModelForm): + class Meta: + model = Article + + def test_foreign_object_form(self): + # A very crude test checking that the non-concrete fields do not get form fields. + form = FormsTests.ArticleForm() + self.assertIn('id_pub_date', form.as_table()) + self.assertNotIn('active_translation', form.as_table()) + form = FormsTests.ArticleForm(data={'pub_date': str(datetime.date.today())}) + self.assertTrue(form.is_valid()) + a = form.save() + self.assertEqual(a.pub_date, datetime.date.today()) + form = FormsTests.ArticleForm(instance=a, data={'pub_date': '2013-01-01'}) + a2 = form.save() + self.assertEqual(a.pk, a2.pk) + self.assertEqual(a2.pub_date, datetime.date(2013, 1, 1)) diff --git a/tests/regressiontests/__init__.py b/tests/forms_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/__init__.py +++ b/tests/forms_tests/__init__.py diff --git a/tests/regressiontests/forms/models.py b/tests/forms_tests/models.py index bec31d12d7..bec31d12d7 100644 --- a/tests/regressiontests/forms/models.py +++ b/tests/forms_tests/models.py diff --git a/tests/regressiontests/forms/templates/forms/article_form.html b/tests/forms_tests/templates/forms_tests/article_form.html index cde85051f6..de38466335 100644 --- a/tests/regressiontests/forms/templates/forms/article_form.html +++ b/tests/forms_tests/templates/forms_tests/article_form.html @@ -1,6 +1,6 @@ <html> <body> - <form method="post" action="."> + <form method="post" action=".">{% csrf_token %} {{ form.as_p }}<br> <input id="submit" type="submit"> </form> diff --git a/tests/forms_tests/tests/__init__.py b/tests/forms_tests/tests/__init__.py new file mode 100644 index 0000000000..aea9418c05 --- /dev/null +++ b/tests/forms_tests/tests/__init__.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import + +from .test_error_messages import (FormsErrorMessagesTestCase, + ModelChoiceFieldErrorMessagesTestCase) +from .test_extra import FormsExtraTestCase, FormsExtraL10NTestCase +from .test_fields import FieldsTests +from .test_forms import FormsTestCase +from .test_formsets import (FormsFormsetTestCase, FormsetAsFooTests, + TestIsBoundBehavior, TestEmptyFormSet) +from .test_input_formats import (LocalizedTimeTests, CustomTimeInputFormatsTests, + SimpleTimeFormatTests, LocalizedDateTests, CustomDateInputFormatsTests, + SimpleDateFormatTests, LocalizedDateTimeTests, + CustomDateTimeInputFormatsTests, SimpleDateTimeFormatTests) +from .test_media import FormsMediaTestCase, StaticFormsMediaTestCase +from .tests import (TestTicket12510, TestTicket14567, ModelFormCallableModelDefault, + FormsModelTestCase, RelatedModelFormTests) +from .test_regressions import FormsRegressionsTestCase +from .test_util import FormsUtilTestCase +from .test_validators import TestFieldWithValidators +from .test_widgets import (FormsWidgetTestCase, FormsI18NWidgetsTestCase, + WidgetTests, LiveWidgetTests, ClearableFileInputTests) diff --git a/tests/regressiontests/forms/tests/filepath_test_files/.dot-file b/tests/forms_tests/tests/filepath_test_files/.dot-file index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/forms/tests/filepath_test_files/.dot-file +++ b/tests/forms_tests/tests/filepath_test_files/.dot-file diff --git a/tests/regressiontests/forms/tests/filepath_test_files/directory/.keep b/tests/forms_tests/tests/filepath_test_files/directory/.keep index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/forms/tests/filepath_test_files/directory/.keep +++ b/tests/forms_tests/tests/filepath_test_files/directory/.keep diff --git a/tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg b/tests/forms_tests/tests/filepath_test_files/fake-image.jpg index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg +++ b/tests/forms_tests/tests/filepath_test_files/fake-image.jpg diff --git a/tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt b/tests/forms_tests/tests/filepath_test_files/real-text-file.txt index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt +++ b/tests/forms_tests/tests/filepath_test_files/real-text-file.txt diff --git a/tests/regressiontests/forms/tests/error_messages.py b/tests/forms_tests/tests/test_error_messages.py index b76e122bc0..9d45a46a3d 100644 --- a/tests/regressiontests/forms/tests/error_messages.py +++ b/tests/forms_tests/tests/test_error_messages.py @@ -60,9 +60,9 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): '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', + 'max_digits': 'MAX DIGITS IS %(max)s', + 'max_decimal_places': 'MAX DP IS %(max)s', + 'max_whole_digits': 'MAX DIGITS BEFORE DP IS %(max)s', } f = DecimalField(min_value=5, max_value=10, error_messages=e) self.assertFormErrors(['REQUIRED'], f.clean, '') @@ -237,7 +237,7 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): def test_modelchoicefield(self): # Create choices for the model choice field tests below. - from regressiontests.forms.models import ChoiceModel + from forms_tests.models import ChoiceModel c1 = ChoiceModel.objects.create(pk=1, name='a') c2 = ChoiceModel.objects.create(pk=2, name='b') c3 = ChoiceModel.objects.create(pk=3, name='c') @@ -254,7 +254,7 @@ class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): # ModelMultipleChoiceField e = { 'required': 'REQUIRED', - 'invalid_choice': '%s IS INVALID CHOICE', + 'invalid_choice': '%(value)s IS INVALID CHOICE', 'list': 'NOT A LIST OF VALUES', } f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) diff --git a/tests/regressiontests/forms/tests/extra.py b/tests/forms_tests/tests/test_extra.py index 07acb29741..a83cdfc05f 100644 --- a/tests/regressiontests/forms/tests/extra.py +++ b/tests/forms_tests/tests/test_extra.py @@ -12,7 +12,7 @@ from django.utils import six from django.utils import translation from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible -from .error_messages import AssertFormErrorsMixin +from .test_error_messages import AssertFormErrorsMixin class GetDate(Form): @@ -428,6 +428,23 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): # If insufficient data is provided, None is substituted self.assertFormErrors(['This field is required.'], f.clean, ['some text',['JP']]) + # test with no initial data + self.assertTrue(f._has_changed(None, ['some text', ['J','P'], ['2007-04-25','6:24:00']])) + + # test when the data is the same as initial + self.assertFalse(f._has_changed('some text,JP,2007-04-25 06:24:00', + ['some text', ['J','P'], ['2007-04-25','6:24:00']])) + + # test when the first widget's data has changed + self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00', + ['other text', ['J','P'], ['2007-04-25','6:24:00']])) + + # test when the last widget's data has changed. this ensures that it is not + # short circuiting while testing the widgets. + self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00', + ['some text', ['J','P'], ['2009-04-25','11:44:00']])) + + class ComplexFieldForm(Form): field1 = ComplexField(widget=w) @@ -457,7 +474,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): f = IPAddressField() self.assertFormErrors(['This field is required.'], f.clean, '') self.assertFormErrors(['This field is required.'], f.clean, None) - self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertEqual(f.clean(' 127.0.0.1'), '127.0.0.1') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'foo') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '127.0.0.') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') @@ -466,7 +483,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): f = IPAddressField(required=False) self.assertEqual(f.clean(''), '') self.assertEqual(f.clean(None), '') - self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertEqual(f.clean(' 127.0.0.1'), '127.0.0.1') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'foo') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '127.0.0.') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') @@ -482,24 +499,24 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): f = GenericIPAddressField() self.assertFormErrors(['This field is required.'], f.clean, '') self.assertFormErrors(['This field is required.'], f.clean, None) - self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertEqual(f.clean(' 127.0.0.1 '), '127.0.0.1') self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo') self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.') self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5') self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5') - self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), 'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), '2a02::223:6cff:fe8a:2e8a') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2') + self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '12345:2:3:4') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1::2:3::4') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1:2') def test_generic_ipaddress_as_ipv4_only(self): f = GenericIPAddressField(protocol="IPv4") self.assertFormErrors(['This field is required.'], f.clean, '') self.assertFormErrors(['This field is required.'], f.clean, None) - self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + self.assertEqual(f.clean(' 127.0.0.1 '), '127.0.0.1') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, 'foo') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '127.0.0.') self.assertFormErrors(['Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5') @@ -516,13 +533,13 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '127.0.0.') self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5') self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '256.125.1.5') - self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), 'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), '2a02::223:6cff:fe8a:2e8a') - self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '12345:2:3:4') - self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1::2:3::4') - self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') - self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') - self.assertFormErrors(['Enter a valid IPv6 address.'], f.clean, '1:2') + self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '12345:2:3:4') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1::2:3::4') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1:2') def test_generic_ipaddress_as_generic_not_required(self): f = GenericIPAddressField(required=False) @@ -533,24 +550,24 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.') self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5') self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5') - self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), 'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), '2a02::223:6cff:fe8a:2e8a') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') - self.assertFormErrors(['Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2') + self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '12345:2:3:4') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1::2:3::4') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8') + self.assertFormErrors(['This is not a valid IPv6 address.'], f.clean, '1:2') def test_generic_ipaddress_normalization(self): # Test the normalising code f = GenericIPAddressField() - self.assertEqual(f.clean('::ffff:0a0a:0a0a'), '::ffff:10.10.10.10') - self.assertEqual(f.clean('::ffff:10.10.10.10'), '::ffff:10.10.10.10') - self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') - self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') + self.assertEqual(f.clean(' ::ffff:0a0a:0a0a '), '::ffff:10.10.10.10') + self.assertEqual(f.clean(' ::ffff:10.10.10.10 '), '::ffff:10.10.10.10') + self.assertEqual(f.clean(' 2001:000:a:0000:0:fe:fe:beef '), '2001:0:a::fe:fe:beef') + self.assertEqual(f.clean(' 2001::a:0000:0:fe:fe:beef '), '2001:0:a::fe:fe:beef') f = GenericIPAddressField(unpack_ipv4=True) - self.assertEqual(f.clean('::ffff:0a0a:0a0a'), '10.10.10.10') + self.assertEqual(f.clean(' ::ffff:0a0a:0a0a'), '10.10.10.10') def test_smart_text(self): class Test: @@ -614,7 +631,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin): f = CommentForm(data, auto_id=False, error_class=DivErrorList) self.assertHTMLEqual(f.as_p(), """<p>Name: <input type="text" name="name" maxlength="50" /></p> <div class="errorlist"><div class="error">Enter a valid email address.</div></div> -<p>Email: <input type="text" name="email" value="invalid" /></p> +<p>Email: <input type="email" 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>""") @@ -725,8 +742,8 @@ class FormsExtraL10NTestCase(TestCase): def test_l10n_date_changed(self): """ - Ensure that SelectDateWidget._has_changed() works correctly with a - localized date format. + Ensure that DateField._has_changed() with SelectDateWidget works + correctly with a localized date format. Refs #17165. """ # With Field.show_hidden_initial=False ----------------------- diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/forms_tests/tests/test_fields.py index e17d976fcf..7516de29b4 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/forms_tests/tests/test_fields.py @@ -35,7 +35,9 @@ from decimal import Decimal from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import * from django.test import SimpleTestCase +from django.utils import formats from django.utils import six +from django.utils import translation from django.utils._os import upath @@ -52,10 +54,29 @@ def fix_os_paths(x): class FieldsTests(SimpleTestCase): + def assertWidgetRendersTo(self, field, to): + class _Form(Form): + f = field + self.assertHTMLEqual(str(_Form()['f']), to) + def test_field_sets_widget_is_required(self): self.assertTrue(Field(required=True).widget.is_required) self.assertFalse(Field(required=False).widget.is_required) + def test_cooperative_multiple_inheritance(self): + class A(object): + def __init__(self): + self.class_a_var = True + super(A, self).__init__() + + + class ComplexField(Field, A): + def __init__(self): + super(ComplexField, self).__init__() + + f = ComplexField() + self.assertTrue(f.class_a_var) + # CharField ################################################################### def test_charfield_1(self): @@ -125,6 +146,7 @@ class FieldsTests(SimpleTestCase): def test_integerfield_1(self): f = IntegerField() + self.assertWidgetRendersTo(f, '<input type="number" name="f" id="id_f" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(1, f.clean('1')) @@ -159,6 +181,7 @@ class FieldsTests(SimpleTestCase): def test_integerfield_3(self): f = IntegerField(max_value=10) + self.assertWidgetRendersTo(f, '<input max="10" type="number" name="f" id="id_f" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(1, f.clean(1)) self.assertEqual(10, f.clean(10)) @@ -170,6 +193,7 @@ class FieldsTests(SimpleTestCase): def test_integerfield_4(self): f = IntegerField(min_value=10) + self.assertWidgetRendersTo(f, '<input id="id_f" type="number" name="f" min="10" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1) self.assertEqual(10, f.clean(10)) @@ -181,6 +205,7 @@ class FieldsTests(SimpleTestCase): def test_integerfield_5(self): f = IntegerField(min_value=10, max_value=20) + self.assertWidgetRendersTo(f, '<input id="id_f" max="20" type="number" name="f" min="10" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1) self.assertEqual(10, f.clean(10)) @@ -192,10 +217,19 @@ class FieldsTests(SimpleTestCase): self.assertEqual(f.max_value, 20) self.assertEqual(f.min_value, 10) + def test_integerfield_localized(self): + """ + Make sure localized IntegerField's widget renders to a text input with + no number input specific attributes. + """ + f1 = IntegerField(localize=True) + self.assertWidgetRendersTo(f1, '<input id="id_f" name="f" type="text" />') + # FloatField ################################################################## def test_floatfield_1(self): f = FloatField() + self.assertWidgetRendersTo(f, '<input step="any" type="number" name="f" id="id_f" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(1.0, f.clean('1')) @@ -222,6 +256,7 @@ class FieldsTests(SimpleTestCase): def test_floatfield_3(self): f = FloatField(max_value=1.5, min_value=0.5) + self.assertWidgetRendersTo(f, '<input step="any" name="f" min="0.5" max="1.5" type="number" id="id_f" />') self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6') self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4') self.assertEqual(1.5, f.clean('1.5')) @@ -229,10 +264,30 @@ class FieldsTests(SimpleTestCase): self.assertEqual(f.max_value, 1.5) self.assertEqual(f.min_value, 0.5) + def test_floatfield_localized(self): + """ + Make sure localized FloatField's widget renders to a text input with + no number input specific attributes. + """ + f = FloatField(localize=True) + self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />') + + def test_floatfield_changed(self): + f = FloatField() + n = 4.35 + self.assertFalse(f._has_changed(n, '4.3500')) + + with translation.override('fr'): + with self.settings(USE_L10N=True): + f = FloatField(localize=True) + localized_n = formats.localize_input(n) # -> '4,35' in French + self.assertFalse(f._has_changed(n, localized_n)) + # DecimalField ################################################################ def test_decimalfield_1(self): f = DecimalField(max_digits=4, decimal_places=2) + self.assertWidgetRendersTo(f, '<input id="id_f" step="0.01" type="number" name="f" maxlength="6" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual(f.clean('1'), Decimal("1")) @@ -278,6 +333,7 @@ class FieldsTests(SimpleTestCase): def test_decimalfield_3(self): f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5')) + self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.5" max="1.5" maxlength="6" type="number" id="id_f" />') self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6') self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4') self.assertEqual(f.clean('1.5'), Decimal("1.5")) @@ -309,6 +365,26 @@ class FieldsTests(SimpleTestCase): self.assertEqual(f.clean('.01'), Decimal(".01")) self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1') + def test_decimalfield_localized(self): + """ + Make sure localized DecimalField's widget renders to a text input with + no number input specific attributes. + """ + f = DecimalField(localize=True) + self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />') + + def test_decimalfield_changed(self): + f = DecimalField(max_digits=2, decimal_places=2) + d = Decimal("0.1") + self.assertFalse(f._has_changed(d, '0.10')) + self.assertTrue(f._has_changed(d, '0.101')) + + with translation.override('fr'): + with self.settings(USE_L10N=True): + f = DecimalField(max_digits=2, decimal_places=2, localize=True) + localized_d = formats.localize_input(d) # -> '0,1' in French + self.assertFalse(f._has_changed(d, localized_d)) + # DateField ################################################################### def test_datefield_1(self): @@ -362,6 +438,22 @@ class FieldsTests(SimpleTestCase): f = DateField() self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, 'a\x00b') + def test_datefield_changed(self): + format = '%d/%m/%Y' + f = DateField(input_formats=[format]) + d = datetime.date(2007, 9, 17) + self.assertFalse(f._has_changed(d, '17/09/2007')) + + def test_datefield_strptime(self): + """Test that field.strptime doesn't raise an UnicodeEncodeError (#16123)""" + f = DateField() + try: + f.strptime('31 мая 2011', '%d-%b-%y') + except Exception as e: + # assertIsInstance or assertRaises cannot be used because UnicodeEncodeError + # is a subclass of ValueError + self.assertEqual(e.__class__, ValueError) + # TimeField ################################################################### def test_timefield_1(self): @@ -388,6 +480,14 @@ class FieldsTests(SimpleTestCase): self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 ')) self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ' ') + def test_timefield_changed(self): + t1 = datetime.time(12, 51, 34, 482548) + t2 = datetime.time(12, 51) + f = TimeField(input_formats=['%H:%M', '%H:%M %p']) + self.assertTrue(f._has_changed(t1, '12:51')) + self.assertFalse(f._has_changed(t2, '12:51')) + self.assertFalse(f._has_changed(t2, '12:51 PM')) + # DateTimeField ############################################################### def test_datetimefield_1(self): @@ -446,6 +546,13 @@ class FieldsTests(SimpleTestCase): def test_datetimefield_5(self): f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f']) self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002')) + + def test_datetimefield_changed(self): + format = '%Y %m %d %I:%M %p' + f = DateTimeField(input_formats=[format]) + d = datetime.datetime(2006, 9, 17, 14, 30, 0) + self.assertFalse(f._has_changed(d, '2006 09 17 2:30 PM')) + # RegexField ################################################################## def test_regexfield_1(self): @@ -502,10 +609,11 @@ class FieldsTests(SimpleTestCase): self.assertRaisesMessage(ValidationError, "'Enter a valid value.'", f.clean, 'abcd') # EmailField ################################################################## - # See also modeltests/validators tests for validate_email specific tests + # See also validators tests for validate_email specific tests def test_emailfield_1(self): f = EmailField() + self.assertWidgetRendersTo(f, '<input type="email" name="f" id="id_f" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual('person@example.com', f.clean('person@example.com')) @@ -530,6 +638,7 @@ class FieldsTests(SimpleTestCase): def test_emailfield_min_max_length(self): f = EmailField(min_length=10, max_length=15) + self.assertWidgetRendersTo(f, '<input id="id_f" type="email" name="f" maxlength="15" />') self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 10 characters (it has 9).'", f.clean, 'a@foo.com') self.assertEqual('alf@foo.com', f.clean('alf@foo.com')) self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 15 characters (it has 20).'", f.clean, 'alf123456788@foo.com') @@ -566,10 +675,34 @@ class FieldsTests(SimpleTestCase): self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'')))) + def test_filefield_changed(self): + ''' + Test for the behavior of _has_changed for FileField. 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 FileField it is ignored. + ''' + f = FileField() + + # No file was uploaded and no initial data. + self.assertFalse(f._has_changed('', None)) + + # A file was uploaded and no initial data. + self.assertTrue(f._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'})) + + # A file was not uploaded, but there is initial data + self.assertFalse(f._has_changed('resume.txt', None)) + + # A file was uploaded and there is initial data (file identity is not dealt + # with here) + self.assertTrue(f._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})) + + # URLField ################################################################## def test_urlfield_1(self): f = URLField() + self.assertWidgetRendersTo(f, '<input type="url" name="f" id="id_f" />') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertEqual('http://localhost/', f.clean('http://localhost')) @@ -621,6 +754,7 @@ class FieldsTests(SimpleTestCase): def test_urlfield_5(self): f = URLField(min_length=15, max_length=20) + self.assertWidgetRendersTo(f, '<input id="id_f" type="url" name="f" maxlength="20" />') self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 15 characters (it has 13).'", f.clean, 'http://f.com') self.assertEqual('http://example.com/', f.clean('http://example.com')) self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 38).'", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com') @@ -709,6 +843,18 @@ class FieldsTests(SimpleTestCase): def test_boolean_picklable(self): self.assertIsInstance(pickle.loads(pickle.dumps(BooleanField())), BooleanField) + def test_booleanfield_changed(self): + f = BooleanField() + self.assertFalse(f._has_changed(None, None)) + self.assertFalse(f._has_changed(None, '')) + self.assertFalse(f._has_changed('', None)) + self.assertFalse(f._has_changed('', '')) + self.assertTrue(f._has_changed(False, 'on')) + self.assertFalse(f._has_changed(True, 'on')) + self.assertTrue(f._has_changed(True, '')) + # Initial value may have mutated to a string due to show_hidden_initial (#19537) + self.assertTrue(f._has_changed('False', 'on')) + # ChoiceField ################################################################# def test_choicefield_1(self): @@ -779,6 +925,11 @@ class FieldsTests(SimpleTestCase): f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) self.assertEqual(None, f.clean('')) + def test_typedchoicefield_has_changed(self): + # has_changed should not trigger required validation + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) + self.assertFalse(f._has_changed(None, '')) + # NullBooleanField ############################################################ def test_nullbooleanfield_1(self): @@ -825,6 +976,16 @@ class FieldsTests(SimpleTestCase): self.assertEqual(False, f.cleaned_data['nullbool1']) self.assertEqual(None, f.cleaned_data['nullbool2']) + def test_nullbooleanfield_changed(self): + f = NullBooleanField() + self.assertTrue(f._has_changed(False, None)) + self.assertTrue(f._has_changed(None, False)) + self.assertFalse(f._has_changed(None, None)) + self.assertFalse(f._has_changed(False, False)) + self.assertTrue(f._has_changed(True, False)) + self.assertTrue(f._has_changed(True, None)) + self.assertTrue(f._has_changed(True, False)) + # MultipleChoiceField ######################################################### def test_multiplechoicefield_1(self): @@ -866,6 +1027,16 @@ class FieldsTests(SimpleTestCase): self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['6']) self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['1','6']) + def test_multiplechoicefield_changed(self): + f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')]) + self.assertFalse(f._has_changed(None, None)) + self.assertFalse(f._has_changed([], None)) + self.assertTrue(f._has_changed(None, ['1'])) + self.assertFalse(f._has_changed([1, 2], ['1', '2'])) + self.assertFalse(f._has_changed([2, 1], ['1', '2'])) + self.assertTrue(f._has_changed([1, 2], ['1'])) + self.assertTrue(f._has_changed([1, 2], ['1', '3'])) + # TypedMultipleChoiceField ############################################################ # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types # will be returned: @@ -908,6 +1079,11 @@ class FieldsTests(SimpleTestCase): f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) self.assertEqual(None, f.clean([])) + def test_typedmultiplechoicefield_has_changed(self): + # has_changed should not trigger required validation + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) + self.assertFalse(f._has_changed(None, '')) + # ComboField ################################################################## def test_combofield_1(self): @@ -997,7 +1173,7 @@ class FieldsTests(SimpleTestCase): f = FilePathField(path=path, allow_folders=True, allow_files=False) f.choices.sort() expected = [ - ('/tests/regressiontests/forms/tests/filepath_test_files/directory', 'directory'), + ('/tests/forms_tests/tests/filepath_test_files/directory', 'directory'), ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) @@ -1006,10 +1182,10 @@ class FieldsTests(SimpleTestCase): f = FilePathField(path=path, allow_folders=True, allow_files=True) f.choices.sort() expected = [ - ('/tests/regressiontests/forms/tests/filepath_test_files/.dot-file', '.dot-file'), - ('/tests/regressiontests/forms/tests/filepath_test_files/directory', 'directory'), - ('/tests/regressiontests/forms/tests/filepath_test_files/fake-image.jpg', 'fake-image.jpg'), - ('/tests/regressiontests/forms/tests/filepath_test_files/real-text-file.txt', 'real-text-file.txt'), + ('/tests/forms_tests/tests/filepath_test_files/.dot-file', '.dot-file'), + ('/tests/forms_tests/tests/filepath_test_files/directory', 'directory'), + ('/tests/forms_tests/tests/filepath_test_files/fake-image.jpg', 'fake-image.jpg'), + ('/tests/forms_tests/tests/filepath_test_files/real-text-file.txt', 'real-text-file.txt'), ] actual = fix_os_paths(f.choices) @@ -1048,3 +1224,9 @@ class FieldsTests(SimpleTestCase): self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', '']) self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10']) self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['', '07:30']) + + def test_splitdatetimefield_changed(self): + f = SplitDateTimeField(input_date_formats=['%d/%m/%Y']) + self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00'])) + self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40'])) + self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41'])) diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/forms_tests/tests/test_forms.py index 1c83ed04d4..45e62a492c 100644 --- a/tests/regressiontests/forms/tests/forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -11,6 +11,7 @@ from django.test import TestCase from django.test.utils import str_prefix from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.safestring import mark_safe +from django.utils import six class Person(Form): @@ -136,11 +137,7 @@ class FormsTestCase(TestCase): self.assertEqual(p.errors['first_name'], ['This field is required.']) self.assertEqual(p.errors['birthday'], ['This field is required.']) self.assertFalse(p.is_valid()) - self.assertHTMLEqual(p.errors.as_ul(), '<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.""") + self.assertDictEqual(p.errors, {'birthday': ['This field is required.'], 'first_name': ['This field is required.']}) self.assertEqual(p.cleaned_data, {'last_name': 'Lennon'}) self.assertEqual(p['first_name'].errors, ['This field is required.']) self.assertHTMLEqual(p['first_name'].errors.as_ul(), '<ul class="errorlist"><li>This field is required.</li></ul>') @@ -248,11 +245,11 @@ class FormsTestCase(TestCase): get_spam = BooleanField() f = SignupForm(auto_id=False) - self.assertHTMLEqual(str(f['email']), '<input type="text" name="email" />') + self.assertHTMLEqual(str(f['email']), '<input type="email" name="email" />') self.assertHTMLEqual(str(f['get_spam']), '<input type="checkbox" name="get_spam" />') f = SignupForm({'email': 'test@example.com', 'get_spam': True}, auto_id=False) - self.assertHTMLEqual(str(f['email']), '<input type="text" name="email" value="test@example.com" />') + self.assertHTMLEqual(str(f['email']), '<input type="email" name="email" value="test@example.com" />') self.assertHTMLEqual(str(f['get_spam']), '<input checked="checked" type="checkbox" name="get_spam" />') # 'True' or 'true' should be rendered without a value attribute @@ -410,7 +407,7 @@ class FormsTestCase(TestCase): # gets a distinct ID, formed by appending an underscore plus the button's # zero-based index. f = FrameworkForm(auto_id='id_%s') - self.assertHTMLEqual(str(f['language']), """<ul> + self.assertHTMLEqual(str(f['language']), """<ul id="id_language"> <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>""") @@ -419,17 +416,17 @@ class FormsTestCase(TestCase): # either as_table() or as_ul(), the label for the RadioSelect will point to the # ID of the *first* radio button. self.assertHTMLEqual(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> +<tr><th><label for="id_language_0">Language:</label></th><td><ul id="id_language"> <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.assertHTMLEqual(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">Language:</label> <ul id="id_language"> <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.assertHTMLEqual(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> +<p><label for="id_language_0">Language:</label> <ul id="id_language"> <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>""") @@ -536,7 +533,7 @@ class FormsTestCase(TestCase): composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple) f = SongForm(auto_id='%s_id') - self.assertHTMLEqual(str(f['composers']), """<ul> + self.assertHTMLEqual(str(f['composers']), """<ul id="composers_id"> <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>""") @@ -955,12 +952,12 @@ class FormsTestCase(TestCase): class UserRegistration(Form): username = CharField(max_length=10, label='Your username') password1 = CharField(widget=PasswordInput) - password2 = CharField(widget=PasswordInput, label='Password (again)') + password2 = CharField(widget=PasswordInput, label='Contraseña (de nuevo)') p = UserRegistration(auto_id=False) self.assertHTMLEqual(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>""") +<li>Contraseña (de nuevo): <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. @@ -982,14 +979,6 @@ class FormsTestCase(TestCase): <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='\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') - - p = UserRegistration(auto_id=False) - self.assertHTMLEqual(p.as_ul(), '<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='') @@ -1204,6 +1193,32 @@ class FormsTestCase(TestCase): <option value="w">whiz</option> </select></li>""") + def test_changed_data(self): + class Person(Form): + first_name = CharField(initial='Hans') + last_name = CharField(initial='Greatel') + birthday = DateField(initial=datetime.date(1974, 8, 16)) + + p = Person(data={'first_name': 'Hans', 'last_name': 'Scrmbl', + 'birthday': '1974-08-16'}) + self.assertTrue(p.is_valid()) + self.assertNotIn('first_name', p.changed_data) + self.assertIn('last_name', p.changed_data) + self.assertNotIn('birthday', p.changed_data) + + # Test that field raising ValidationError is always in changed_data + class PedanticField(forms.Field): + def to_python(self, value): + raise ValidationError('Whatever') + + class Person2(Person): + pedantic = PedanticField(initial='whatever', show_hidden_initial=True) + + p = Person2(data={'first_name': 'Hans', 'last_name': 'Scrmbl', + 'birthday': '1974-08-16', 'initial-pedantic': 'whatever'}) + self.assertFalse(p.is_valid()) + self.assertIn('pedantic', p.changed_data) + def test_boundfield_values(self): # It's possible to get to the value which would be used for rendering # the widget for a field by using the BoundField's value method. @@ -1223,20 +1238,20 @@ class FormsTestCase(TestCase): # 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.') + password = CharField(widget=PasswordInput, help_text='Wählen Sie mit Bedacht.') p = UserRegistration(auto_id=False) self.assertHTMLEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /> <span class="helptext">e.g., user@example.com</span></li> -<li>Password: <input type="password" name="password" /> <span class="helptext">Choose wisely.</span></li>""") +<li>Password: <input type="password" name="password" /> <span class="helptext">Wählen Sie mit Bedacht.</span></li>""") self.assertHTMLEqual(p.as_p(), """<p>Username: <input type="text" name="username" maxlength="10" /> <span class="helptext">e.g., user@example.com</span></p> -<p>Password: <input type="password" name="password" /> <span class="helptext">Choose wisely.</span></p>""") +<p>Password: <input type="password" name="password" /> <span class="helptext">Wählen Sie mit Bedacht.</span></p>""") self.assertHTMLEqual(p.as_table(), """<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /><br /><span class="helptext">e.g., user@example.com</span></td></tr> -<tr><th>Password:</th><td><input type="password" name="password" /><br /><span class="helptext">Choose wisely.</span></td></tr>""") +<tr><th>Password:</th><td><input type="password" name="password" /><br /><span class="helptext">Wählen Sie mit Bedacht.</span></td></tr>""") # The help text is displayed whether or not data is provided for the form. p = UserRegistration({'username': 'foo'}, auto_id=False) self.assertHTMLEqual(p.as_ul(), """<li>Username: <input type="text" name="username" value="foo" maxlength="10" /> <span class="helptext">e.g., user@example.com</span></li> -<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /> <span class="helptext">Choose wisely.</span></li>""") +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /> <span class="helptext">Wählen Sie mit Bedacht.</span></li>""") # help_text is not displayed for hidden fields. It can be used for documentation # purposes, though. @@ -1249,13 +1264,6 @@ class FormsTestCase(TestCase): self.assertHTMLEqual(p.as_ul(), """<li>Username: <input type="text" name="username" maxlength="10" /> <span class="helptext">e.g., user@example.com</span></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.assertHTMLEqual(p.as_ul(), '<li>Username: <input type="text" name="username" maxlength="10" /> <span class="helptext">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</span></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 @@ -1495,7 +1503,7 @@ class FormsTestCase(TestCase): form = UserRegistration(auto_id=False) if form.is_valid(): - return 'VALID: %r' % form.cleaned_data + return 'VALID: %r' % sorted(six.iteritems(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})) @@ -1520,7 +1528,8 @@ class FormsTestCase(TestCase): <input type="submit" /> </form>""") # Case 3: POST with valid data (the success message).) - self.assertHTMLEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}), str_prefix("VALID: {'username': %(_)s'adrian', 'password1': %(_)s'secret', 'password2': %(_)s'secret'}")) + self.assertEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}), + str_prefix("VALID: [('password1', %(_)s'secret'), ('password2', %(_)s'secret'), ('username', %(_)s'adrian')]")) def test_templates_with_forms(self): class UserRegistration(Form): @@ -1614,23 +1623,6 @@ class FormsTestCase(TestCase): </form>""") self.assertEqual(Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})), '') - # The label_tag() method takes an optional attrs argument: a dictionary of HTML - # attributes to add to the <label> tag. - f = UserRegistration(auto_id='id_%s') - form_output = [] - - for bf in f: - form_output.append(bf.label_tag(attrs={'class': 'pretty'})) - - expected_form_output = [ - '<label for="id_username" class="pretty">Username</label>', - '<label for="id_password1" class="pretty">Password1</label>', - '<label for="id_password2" class="pretty">Password2</label>', - ] - self.assertEqual(len(form_output), len(expected_form_output)) - for i in range(len(form_output)): - self.assertHTMLEqual(form_output[i], expected_form_output[i]) - # To display the errors that aren't associated with a particular field -- e.g., # the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the # template. If used on its own, it is displayed as a <ul> (or an empty string, if @@ -1741,8 +1733,8 @@ class FormsTestCase(TestCase): <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>""") +<li><label for="id_email">Email:</label> <input type="email" 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="number" name="age" id="id_age" /></li>""") self.assertHTMLEqual(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> @@ -1751,9 +1743,9 @@ class FormsTestCase(TestCase): <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> +<p><label for="id_email">Email:</label> <input type="email" 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>""") +<p class="required error"><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" /></p>""") self.assertHTMLEqual(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"> @@ -1761,8 +1753,8 @@ class FormsTestCase(TestCase): <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>""") +<tr><th><label for="id_email">Email:</label></th><td><input type="email" 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="number" name="age" id="id_age" /></td></tr>""") def test_label_split_datetime_not_displayed(self): class EventForm(Form): @@ -1799,3 +1791,58 @@ class FormsTestCase(TestCase): form = NameForm(data={'name' : ['fname', 'lname']}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data, {'name' : 'fname lname'}) + + def test_custom_empty_values(self): + """ + Test that form fields can customize what is considered as an empty value + for themselves (#19997). + """ + class CustomJSONField(CharField): + empty_values = [None, ''] + def to_python(self, value): + # Fake json.loads + if value == '{}': + return {} + return super(CustomJSONField, self).to_python(value) + + class JSONForm(forms.Form): + json = CustomJSONField() + + form = JSONForm(data={'json': '{}'}); + form.full_clean() + self.assertEqual(form.cleaned_data, {'json' : {}}) + + def test_boundfield_label_tag(self): + class SomeForm(Form): + field = CharField() + boundfield = SomeForm()['field'] + + testcases = [ # (args, kwargs, expected) + # without anything: just print the <label> + ((), {}, '<label for="id_field">Field</label>'), + + # passing just one argument: overrides the field's label + (('custom',), {}, '<label for="id_field">custom</label>'), + + # the overriden label is escaped + (('custom&',), {}, '<label for="id_field">custom&</label>'), + ((mark_safe('custom&'),), {}, '<label for="id_field">custom&</label>'), + + # Passing attrs to add extra attributes on the <label> + ((), {'attrs': {'class': 'pretty'}}, '<label for="id_field" class="pretty">Field</label>') + ] + + for args, kwargs, expected in testcases: + self.assertHTMLEqual(boundfield.label_tag(*args, **kwargs), expected) + + def test_boundfield_label_tag_no_id(self): + """ + If a widget has no id, label_tag just returns the text with no + surrounding <label>. + """ + class SomeForm(Form): + field = CharField() + boundfield = SomeForm(auto_id='')['field'] + + self.assertHTMLEqual(boundfield.label_tag(), 'Field') + self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&') diff --git a/tests/regressiontests/forms/tests/formsets.py b/tests/forms_tests/tests/test_formsets.py index bf893c4c1d..4ac3c5ecf1 100644 --- a/tests/regressiontests/forms/tests/formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.forms import Form, CharField, IntegerField, ValidationError, DateField -from django.forms.formsets import formset_factory, BaseFormSet +from django.forms import (CharField, DateField, FileField, Form, IntegerField, + ValidationError, formsets) +from django.forms.formsets import BaseFormSet, formset_factory from django.forms.util import ErrorList from django.test import TestCase @@ -50,9 +51,9 @@ class FormsFormsetTestCase(TestCase): # for adding data. By default, it displays 1 blank form. It can display more, # but we'll look at how to do so later. formset = ChoiceFormSet(auto_id=False, prefix='choices') - self.assertHTMLEqual(str(formset), """<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" /> + self.assertHTMLEqual(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" value="1000" /> <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>""") +<tr><th>Votes:</th><td><input type="number" 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 @@ -136,9 +137,9 @@ class FormsFormsetTestCase(TestCase): form_output.append(form.as_ul()) self.assertHTMLEqual('\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>Votes: <input type="number" 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>Votes: <input type="number" name="choices-1-votes" /></li>""") # Let's simulate what would happen if we submitted this form. @@ -209,11 +210,11 @@ class FormsFormsetTestCase(TestCase): form_output.append(form.as_ul()) self.assertHTMLEqual('\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>Votes: <input type="number" 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>Votes: <input type="number" 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>Votes: <input type="number" 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 @@ -255,6 +256,27 @@ class FormsFormsetTestCase(TestCase): self.assertTrue(formset.is_valid()) self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': 'Calexico'}, {}, {}]) + def test_formset_validate_max_flag(self): + # If validate_max is set and max_num is less than TOTAL_FORMS in the + # data, then throw an exception. MAX_NUM_FORMS in the data is + # irrelevant here (it's output as a hint for the client but its + # value in the returned data is not checked) + + data = { + 'choices-TOTAL_FORMS': '2', # the number of forms rendered + 'choices-INITIAL_FORMS': '0', # the number of forms with initial data + 'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored + 'choices-0-choice': 'Zero', + 'choices-0-votes': '0', + 'choices-1-choice': 'One', + 'choices-1-votes': '1', + } + + ChoiceFormSet = formset_factory(Choice, extra=1, max_num=1, validate_max=True) + formset = ChoiceFormSet(data, auto_id=False, prefix='choices') + self.assertFalse(formset.is_valid()) + self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.']) + def test_second_form_partially_filled_2(self): # And once again, if we try to partially complete a form, validation will fail. @@ -300,19 +322,19 @@ class FormsFormsetTestCase(TestCase): form_output.append(form.as_ul()) self.assertHTMLEqual('\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>Votes: <input type="number" 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>Votes: <input type="number" 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>Votes: <input type="number" 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>""") +<li>Votes: <input type="number" 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.assertHTMLEqual(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>""") +<li>Votes: <input type="number" name="choices-__prefix__-votes" /></li>""") def test_formset_with_deletion(self): # FormSets with deletion ###################################################### @@ -330,13 +352,13 @@ class FormsFormsetTestCase(TestCase): form_output.append(form.as_ul()) self.assertHTMLEqual('\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>Votes: <input type="number" 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>Votes: <input type="number" 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>Votes: <input type="number" 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 @@ -427,14 +449,14 @@ class FormsFormsetTestCase(TestCase): form_output.append(form.as_ul()) self.assertHTMLEqual('\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>Votes: <input type="number" name="choices-0-votes" value="100" /></li> +<li>Order: <input type="number" 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>Votes: <input type="number" name="choices-1-votes" value="900" /></li> +<li>Order: <input type="number" 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>""") +<li>Votes: <input type="number" name="choices-2-votes" /></li> +<li>Order: <input type="number" name="choices-2-ORDER" /></li>""") data = { 'choices-TOTAL_FORMS': '3', # the number of forms rendered @@ -538,20 +560,20 @@ class FormsFormsetTestCase(TestCase): form_output.append(form.as_ul()) self.assertHTMLEqual('\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>Votes: <input type="number" name="choices-0-votes" value="100" /></li> +<li>Order: <input type="number" 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>Votes: <input type="number" name="choices-1-votes" value="900" /></li> +<li>Order: <input type="number" 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>Votes: <input type="number" name="choices-2-votes" value="500" /></li> +<li>Order: <input type="number" 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>Votes: <input type="number" name="choices-3-votes" /></li> +<li>Order: <input type="number" 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. @@ -653,8 +675,8 @@ class FormsFormsetTestCase(TestCase): # 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. + # When not passed, max_num will take a high default value, leaving the + # number of forms only controlled by the value of the extra parameter. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3) formset = LimitedFavoriteDrinkFormSet() @@ -701,8 +723,8 @@ class FormsFormsetTestCase(TestCase): 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 + # When not passed, max_num will take a high default value, leaving the + # number of forms only controlled by the value of the initial and extra # parameters. initial = [ @@ -719,8 +741,20 @@ class FormsFormsetTestCase(TestCase): <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. + # If max_num is 0 then no form is rendered at all, regardless of extra, + # unless initial data is present. (This changed in the patch for bug + # 20084 -- previously max_num=0 trumped initial data) + + LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0) + formset = LimitedFavoriteDrinkFormSet() + form_output = [] + + for form in formset.forms: + form_output.append(str(form)) + + self.assertEqual('\n'.join(form_output), "") + + # test that initial trumps max_num initial = [ {'name': 'Fernet and Coke'}, @@ -732,12 +766,13 @@ class FormsFormsetTestCase(TestCase): for form in formset.forms: form_output.append(str(form)) - - self.assertEqual('\n'.join(form_output), "") + self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" name="form-0-name" type="text" value="Fernet and Coke" /></td></tr> +<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" name="form-1-name" type="text" value="Bloody Mary" /></td></tr>""") 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. + # More initial forms than max_num now results in all initial forms + # being displayed (but no extra forms). This behavior was changed + # from max_num taking precedence in the patch for #20084 initial = [ {'name': 'Gin Tonic'}, @@ -750,9 +785,9 @@ class FormsFormsetTestCase(TestCase): for form in formset.forms: form_output.append(str(form)) - - self.assertHTMLEqual('\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>""") + self.assertHTMLEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" name="form-0-name" type="text" value="Gin Tonic" /></td></tr> +<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" name="form-1-name" type="text" value="Bloody Mary" /></td></tr> +<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" name="form-2-name" type="text" value="Jack and Coke" /></td></tr>""") # One form from initial and extra=3 with max_num=2 should result in the one # initial form and one extra. @@ -877,6 +912,66 @@ class FormsFormsetTestCase(TestCase): self.assertTrue(formset.is_valid()) self.assertTrue(all([form.is_valid_called for form in formset.forms])) + def test_hard_limit_on_instantiated_forms(self): + """A formset has a hard limit on the number of forms instantiated.""" + # reduce the default limit of 1000 temporarily for testing + _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM + try: + formsets.DEFAULT_MAX_NUM = 2 + ChoiceFormSet = formset_factory(Choice, max_num=1) + # someone fiddles with the mgmt form data... + formset = ChoiceFormSet( + { + 'choices-TOTAL_FORMS': '4', + 'choices-INITIAL_FORMS': '0', + 'choices-MAX_NUM_FORMS': '4', + 'choices-0-choice': 'Zero', + 'choices-0-votes': '0', + 'choices-1-choice': 'One', + 'choices-1-votes': '1', + 'choices-2-choice': 'Two', + 'choices-2-votes': '2', + 'choices-3-choice': 'Three', + 'choices-3-votes': '3', + }, + prefix='choices', + ) + # But we still only instantiate 3 forms + self.assertEqual(len(formset.forms), 3) + # and the formset isn't valid + self.assertFalse(formset.is_valid()) + finally: + formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM + + def test_increase_hard_limit(self): + """Can increase the built-in forms limit via a higher max_num.""" + # reduce the default limit of 1000 temporarily for testing + _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM + try: + formsets.DEFAULT_MAX_NUM = 3 + # for this form, we want a limit of 4 + ChoiceFormSet = formset_factory(Choice, max_num=4) + formset = ChoiceFormSet( + { + 'choices-TOTAL_FORMS': '4', + 'choices-INITIAL_FORMS': '0', + 'choices-MAX_NUM_FORMS': '4', + 'choices-0-choice': 'Zero', + 'choices-0-votes': '0', + 'choices-1-choice': 'One', + 'choices-1-votes': '1', + 'choices-2-choice': 'Two', + 'choices-2-votes': '2', + 'choices-3-choice': 'Three', + 'choices-3-votes': '3', + }, + prefix='choices', + ) + # Four forms are instantiated and no exception is raised + self.assertEqual(len(formset.forms), 4) + finally: + formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM + data = { 'choices-TOTAL_FORMS': '1', # the number of forms rendered @@ -897,19 +992,19 @@ class FormsetAsFooTests(TestCase): formset = ChoiceFormSet(data, auto_id=False, prefix='choices') self.assertHTMLEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" /> <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr> -<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""") +<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" value="100" /></td></tr>""") def test_as_p(self): formset = ChoiceFormSet(data, auto_id=False, prefix='choices') self.assertHTMLEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" /> <p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p> -<p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""") +<p>Votes: <input type="number" name="choices-0-votes" value="100" /></p>""") def test_as_ul(self): formset = ChoiceFormSet(data, auto_id=False, prefix='choices') self.assertHTMLEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" /> <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li> -<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""") +<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>""") # Regression test for #11418 ################################################# @@ -974,11 +1069,23 @@ class TestIsBoundBehavior(TestCase): self.assertHTMLEqual(empty_forms[0].as_p(), empty_forms[1].as_p()) class TestEmptyFormSet(TestCase): - "Test that an empty formset still calls clean()" def test_empty_formset_is_valid(self): + """Test that an empty formset still calls clean()""" EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate) formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form") formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form") self.assertFalse(formset.is_valid()) self.assertFalse(formset2.is_valid()) + def test_empty_formset_media(self): + """Make sure media is available on empty formset, refs #19545""" + class MediaForm(Form): + class Media: + js = ('some-file.js',) + self.assertIn('some-file.js', str(formset_factory(MediaForm, extra=0)().media)) + + def test_empty_formset_is_multipart(self): + """Make sure `is_multipart()` works with empty formset, refs #19545""" + class FileForm(Form): + file = FileField() + self.assertTrue(formset_factory(FileForm, extra=0)().is_multipart()) diff --git a/tests/regressiontests/forms/tests/input_formats.py b/tests/forms_tests/tests/test_input_formats.py index 7a305dbc2b..eb0e8dcad8 100644 --- a/tests/regressiontests/forms/tests/input_formats.py +++ b/tests/forms_tests/tests/test_input_formats.py @@ -9,7 +9,8 @@ from django.test import SimpleTestCase @override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"], USE_L10N=True) class LocalizedTimeTests(SimpleTestCase): def setUp(self): - # nl/formats.py has customized TIME_INPUT_FORMATS + # nl/formats.py has customized TIME_INPUT_FORMATS: + # ('%H:%M:%S', '%H.%M:%S', '%H.%M', '%H:%M') activate('nl') def tearDown(self): @@ -37,6 +38,10 @@ class LocalizedTimeTests(SimpleTestCase): text = f.widget._format_value(result) self.assertEqual(text, "13:30:00") + # ISO formats are accepted, even if not specified in formats.py + result = f.clean('13:30:05.000155') + self.assertEqual(result, time(13,30,5,155)) + def test_localized_timeField(self): "Localized TimeFields act as unlocalized widgets" f = forms.TimeField(localize=True) diff --git a/tests/regressiontests/forms/tests/media.py b/tests/forms_tests/tests/test_media.py index c492a1e539..c492a1e539 100644 --- a/tests/regressiontests/forms/tests/media.py +++ b/tests/forms_tests/tests/test_media.py diff --git a/tests/regressiontests/forms/tests/regressions.py b/tests/forms_tests/tests/test_regressions.py index 45f51eef2f..be9dc8c593 100644 --- a/tests/regressiontests/forms/tests/regressions.py +++ b/tests/forms_tests/tests/test_regressions.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from warnings import catch_warnings +import warnings from django.forms import * from django.test import TestCase from django.utils.translation import ugettext_lazy, override -from regressiontests.forms.models import Cheese +from forms_tests.models import Cheese class FormsRegressionsTestCase(TestCase): @@ -54,22 +54,23 @@ class FormsRegressionsTestCase(TestCase): somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label='\xc5\xf8\xdf') f = SomeForm() - self.assertHTMLEqual(f.as_p(), '<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>') + self.assertHTMLEqual(f.as_p(), '<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul id="id_somechoice">\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 = ((b'\xd0\xbc\xd0\xb5\xd1\x81.', b'\xd0\xbc\xd0\xb5\xd1\x81.'), (b'\xd1\x88\xd1\x82.', b'\xd1\x88\xd1\x82.')) f = ChoiceField(choices=UNITS) - self.assertEqual(f.clean('\u0448\u0442.'), '\u0448\u0442.') - with catch_warnings(record=True): + with warnings.catch_warnings(): # Ignore UnicodeWarning + warnings.simplefilter("ignore") + self.assertEqual(f.clean('\u0448\u0442.'), '\u0448\u0442.') self.assertEqual(f.clean(b'\xd1\x88\xd1\x82.'), '\u0448\u0442.') # Translated error messages used to be buggy. with override('ru'): f = SomeForm({}) - self.assertHTMLEqual(f.as_p(), '<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>') + self.assertHTMLEqual(f.as_p(), '<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 id="id_somechoice">\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>') # Deep copying translated text shouldn't raise an error) from django.utils.translation import gettext_lazy @@ -121,7 +122,7 @@ class FormsRegressionsTestCase(TestCase): f = SomeForm({'field': ['<script>']}) self.assertHTMLEqual(t.render(Context({'form': f})), '<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>') - from regressiontests.forms.models import ChoiceModel + from forms_tests.models import ChoiceModel class SomeForm(Form): field = ModelMultipleChoiceField(ChoiceModel.objects.all()) diff --git a/tests/regressiontests/forms/tests/util.py b/tests/forms_tests/tests/test_util.py index c2d213ae23..c2d213ae23 100644 --- a/tests/regressiontests/forms/tests/util.py +++ b/tests/forms_tests/tests/test_util.py diff --git a/tests/forms_tests/tests/test_validators.py b/tests/forms_tests/tests/test_validators.py new file mode 100644 index 0000000000..0598835cff --- /dev/null +++ b/tests/forms_tests/tests/test_validators.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals + +from django import forms +from django.core import validators +from django.core.exceptions import ValidationError +from django.utils.unittest import TestCase + + +class UserForm(forms.Form): + full_name = forms.CharField( + max_length = 50, + validators = [ + validators.validate_integer, + validators.validate_email, + ] + ) + string = forms.CharField( + max_length = 50, + validators = [ + validators.RegexValidator( + regex='^[a-zA-Z]*$', + message="Letters only.", + ) + ] + ) + + +class TestFieldWithValidators(TestCase): + def test_all_errors_get_reported(self): + form = UserForm({'full_name': 'not int nor mail', 'string': '2 is not correct'}) + self.assertRaises(ValidationError, form.fields['full_name'].clean, 'not int nor mail') + + try: + form.fields['full_name'].clean('not int nor mail') + except ValidationError as e: + self.assertEqual(2, len(e.messages)) + + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['string'], ["Letters only."]) diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/forms_tests/tests/test_widgets.py index 31c0e65c7c..4664553aa7 100644 --- a/tests/regressiontests/forms/tests/widgets.py +++ b/tests/forms_tests/tests/test_widgets.py @@ -15,7 +15,7 @@ from django.utils import six from django.utils.translation import activate, deactivate from django.test import TestCase from django.test.utils import override_settings -from django.utils.encoding import python_2_unicode_compatible +from django.utils.encoding import python_2_unicode_compatible, force_text from ..models import Article @@ -148,25 +148,6 @@ class FormsWidgetTestCase(TestCase): self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<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('', None)) - - # A file was uploaded and no initial data. - self.assertTrue(w._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'})) - - # A file was not uploaded, but there is initial data - self.assertFalse(w._has_changed('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.assertHTMLEqual(w.render('msg', ''), '<textarea rows="10" cols="40" name="msg"></textarea>') @@ -233,14 +214,6 @@ class FormsWidgetTestCase(TestCase): self.assertIsInstance(value, bool) self.assertTrue(value) - self.assertFalse(w._has_changed(None, None)) - self.assertFalse(w._has_changed(None, '')) - self.assertFalse(w._has_changed('', None)) - self.assertFalse(w._has_changed('', '')) - self.assertTrue(w._has_changed(False, 'on')) - self.assertFalse(w._has_changed(True, 'on')) - self.assertTrue(w._has_changed(True, '')) - def test_select(self): w = Select() self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle"> @@ -413,13 +386,6 @@ class FormsWidgetTestCase(TestCase): <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() @@ -533,14 +499,6 @@ class FormsWidgetTestCase(TestCase): # Unicode choices are correctly rendered as HTML self.assertHTMLEqual(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), '<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, ['1'])) - self.assertFalse(w._has_changed([1, 2], ['1', '2'])) - self.assertTrue(w._has_changed([1, 2], ['1'])) - self.assertTrue(w._has_changed([1, 2], ['1', '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.assertHTMLEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice"> @@ -698,7 +656,7 @@ beatle J R Ringo False""") <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 + # A RadioFieldRenderer object also allows index access to individual RadioChoiceInput w = RadioSelect() r = w.get_renderer('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) self.assertHTMLEqual(str(r[1]), '<label><input type="radio" name="beatle" value="P" /> Paul</label>') @@ -707,11 +665,8 @@ beatle J R Ringo False""") self.assertFalse(r[1].is_checked()) self.assertEqual((r[1].name, r[1].value, r[1].choice_value, r[1].choice_label), ('beatle', 'J', 'P', 'Paul')) - try: + with self.assertRaises(IndexError): r[10] - self.fail("This offset should not exist.") - except IndexError: - pass # Choices are escaped correctly w = RadioSelect() @@ -726,7 +681,7 @@ beatle J R Ringo False""") # Attributes provided at instantiation are passed to the constituent inputs w = RadioSelect(attrs={'id':'foo'}) - self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul> + self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<ul id="foo"> <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> @@ -735,7 +690,7 @@ beatle J R Ringo False""") # Attributes provided at render-time are passed to the constituent inputs w = RadioSelect() - self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}), """<ul> + self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}), """<ul id="bar"> <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> @@ -842,25 +797,42 @@ beatle J R Ringo False""") <li><label><input type="checkbox" name="escape" value="good" /> you > 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, ['1'])) - self.assertFalse(w._has_changed([1, 2], ['1', '2'])) - self.assertTrue(w._has_changed([1, 2], ['1'])) - self.assertTrue(w._has_changed([1, 2], ['1', '3'])) - self.assertFalse(w._has_changed([2, 1], ['1', '2'])) - # Unicode choices are correctly rendered as HTML self.assertHTMLEqual(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), '<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.assertHTMLEqual(CheckboxSelectMultiple().render('letters', list('ac'), choices=zip(list('abc'), list('ABC')), attrs={'id': 'abc'}), """<ul> + self.assertHTMLEqual(CheckboxSelectMultiple().render('letters', list('ac'), choices=zip(list('abc'), list('ABC')), attrs={'id': 'abc'}), """<ul id="abc"> +<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>""") + + # Each input gets a separate ID when the ID is passed to the constructor + self.assertHTMLEqual(CheckboxSelectMultiple(attrs={'id': 'abc'}).render('letters', list('ac'), choices=zip(list('abc'), list('ABC'))), """<ul id="abc"> <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>""") + w = CheckboxSelectMultiple() + r = w.get_renderer('abc', 'b', choices=[(c, c.upper()) for c in 'abc']) + # You can iterate over the CheckboxFieldRenderer to get individual elements + expected = [ + '<label><input type="checkbox" name="abc" value="a" /> A</label>', + '<label><input checked="checked" type="checkbox" name="abc" value="b" /> B</label>', + '<label><input type="checkbox" name="abc" value="c" /> C</label>', + ] + for output, expected in zip(r, expected): + self.assertHTMLEqual(force_text(output), expected) + + # You can access individual elements + self.assertHTMLEqual(force_text(r[1]), + '<label><input checked="checked" type="checkbox" name="abc" value="b" /> B</label>') + + # Out-of-range errors are propagated + with self.assertRaises(IndexError): + r[42] + def test_multi(self): class MyMultiWidget(MultiWidget): def decompress(self, value): @@ -877,21 +849,6 @@ beatle J R Ringo False""") w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'}) self.assertHTMLEqual(w.render('name', ['john', 'lennon']), '<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, ['john', 'lennon'])) - - # test when the data is the same as initial - self.assertFalse(w._has_changed('john__lennon', ['john', 'lennon'])) - - # test when the first widget's data has changed - self.assertTrue(w._has_changed('john__lennon', ['alfred', '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('john__lennon', ['john', 'denver'])) - def test_splitdatetime(self): w = SplitDateTimeWidget() self.assertHTMLEqual(w.render('date', ''), '<input type="text" name="date_0" /><input type="text" name="date_1" />') @@ -907,10 +864,6 @@ beatle J R Ringo False""") w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M') self.assertHTMLEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), '<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), ['2008-05-06', '12:40:00'])) - self.assertFalse(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40'])) - self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41'])) - def test_datetimeinput(self): w = DateTimeInput() self.assertHTMLEqual(w.render('date', None), '<input type="text" name="date" />') @@ -925,13 +878,6 @@ beatle J R Ringo False""") # Use 'format' to change the way a value is displayed. w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'}) self.assertHTMLEqual(w.render('date', d), '<input type="datetime" 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() @@ -948,13 +894,6 @@ beatle J R Ringo False""") # Use 'format' to change the way a value is displayed. w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'}) self.assertHTMLEqual(w.render('date', d), '<input type="date" 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() @@ -973,13 +912,6 @@ beatle J R Ringo False""") # Use 'format' to change the way a value is displayed. w = TimeInput(format='%H:%M', attrs={'type': 'time'}) self.assertHTMLEqual(w.render('time', t), '<input type="time" 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 @@ -1007,10 +939,6 @@ class FormsI18NWidgetsTestCase(TestCase): deactivate() 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), ['06.05.2008', '12:41'])) - def test_datetimeinput(self): w = DateTimeInput() d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548) @@ -1100,7 +1028,7 @@ class WidgetTests(TestCase): class LiveWidgetTests(AdminSeleniumWebDriverTestCase): - urls = 'regressiontests.forms.urls' + urls = 'forms_tests.urls' def test_textarea_trailing_newlines(self): """ diff --git a/tests/regressiontests/forms/tests/models.py b/tests/forms_tests/tests/tests.py index be75643b28..80e2cadcc3 100644 --- a/tests/regressiontests/forms/tests/models.py +++ b/tests/forms_tests/tests/tests.py @@ -46,7 +46,8 @@ class TestTicket14567(TestCase): """ def test_empty_queryset_return(self): "If a model's ManyToManyField has blank=True and is saved with no data, a queryset is returned." - form = OptionalMultiChoiceModelForm({'multi_choice_optional': '', 'multi_choice': ['1']}) + option = ChoiceOptionModel.objects.create(name='default') + form = OptionalMultiChoiceModelForm({'multi_choice_optional': '', 'multi_choice': [option.pk]}) self.assertTrue(form.is_valid()) # Check that the empty value is a QuerySet self.assertTrue(isinstance(form.cleaned_data['multi_choice_optional'], models.query.QuerySet)) diff --git a/tests/regressiontests/forms/urls.py b/tests/forms_tests/urls.py index b482b6c45c..cbd760081a 100644 --- a/tests/regressiontests/forms/urls.py +++ b/tests/forms_tests/urls.py @@ -5,5 +5,5 @@ from .views import ArticleFormView urlpatterns = patterns('', - url(r'^/model_form/(?P<pk>\d+)/$', ArticleFormView.as_view(), name="article_form"), + url(r'^model_form/(?P<pk>\d+)/$', ArticleFormView.as_view(), name="article_form"), ) diff --git a/tests/regressiontests/forms/views.py b/tests/forms_tests/views.py index 4bf384363c..4bf384363c 100644 --- a/tests/regressiontests/forms/views.py +++ b/tests/forms_tests/views.py diff --git a/tests/regressiontests/admin_changelist/__init__.py b/tests/generic_inline_admin/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_changelist/__init__.py +++ b/tests/generic_inline_admin/__init__.py diff --git a/tests/regressiontests/generic_inline_admin/admin.py b/tests/generic_inline_admin/admin.py index 73cac7f7c5..73cac7f7c5 100644 --- a/tests/regressiontests/generic_inline_admin/admin.py +++ b/tests/generic_inline_admin/admin.py diff --git a/tests/regressiontests/generic_inline_admin/fixtures/users.xml b/tests/generic_inline_admin/fixtures/users.xml index 6cf441f01e..6cf441f01e 100644 --- a/tests/regressiontests/generic_inline_admin/fixtures/users.xml +++ b/tests/generic_inline_admin/fixtures/users.xml diff --git a/tests/regressiontests/generic_inline_admin/models.py b/tests/generic_inline_admin/models.py index dedc351075..dedc351075 100644 --- a/tests/regressiontests/generic_inline_admin/models.py +++ b/tests/generic_inline_admin/models.py diff --git a/tests/regressiontests/generic_inline_admin/tests.py b/tests/generic_inline_admin/tests.py index fea30b4946..ac2c191183 100644 --- a/tests/regressiontests/generic_inline_admin/tests.py +++ b/tests/generic_inline_admin/tests.py @@ -6,6 +6,7 @@ from django.contrib import admin from django.contrib.admin.sites import AdminSite from django.contrib.contenttypes.generic import ( generic_inlineformset_factory, GenericTabularInline) +from django.forms.formsets import DEFAULT_MAX_NUM from django.forms.models import ModelForm from django.test import TestCase from django.test.utils import override_settings @@ -18,7 +19,7 @@ from .models import (Episode, EpisodeExtra, EpisodeMaxNum, Media, @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class GenericAdminViewTest(TestCase): - urls = "regressiontests.generic_inline_admin.urls" + urls = "generic_inline_admin.urls" fixtures = ['users.xml'] def setUp(self): @@ -102,22 +103,22 @@ class GenericAdminViewTest(TestCase): # Works with no queryset formset = EpisodeMediaFormSet(instance=e) self.assertEqual(len(formset.forms), 5) - self.assertHTMLEqual(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.assertHTMLEqual(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.assertHTMLEqual(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>') + self.assertHTMLEqual(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="url" 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.assertHTMLEqual(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="url" 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.assertHTMLEqual(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="url" 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.assertEqual(len(formset.forms), 5) - self.assertHTMLEqual(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.assertHTMLEqual(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.assertHTMLEqual(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>') + self.assertHTMLEqual(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="url" 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.assertHTMLEqual(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="url" 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.assertHTMLEqual(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="url" 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.assertEqual(len(formset.forms), 4) - self.assertHTMLEqual(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.assertHTMLEqual(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>') + self.assertHTMLEqual(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="url" 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.assertHTMLEqual(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="url" 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. @@ -131,7 +132,7 @@ class GenericAdminViewTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class GenericInlineAdminParametersTest(TestCase): - urls = "regressiontests.generic_inline_admin.urls" + urls = "generic_inline_admin.urls" fixtures = ['users.xml'] def setUp(self): @@ -185,7 +186,7 @@ class GenericInlineAdminParametersTest(TestCase): @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class GenericInlineAdminWithUniqueTogetherTest(TestCase): - urls = "regressiontests.generic_inline_admin.urls" + urls = "generic_inline_admin.urls" fixtures = ['users.xml'] def setUp(self): @@ -212,7 +213,7 @@ class GenericInlineAdminWithUniqueTogetherTest(TestCase): self.assertEqual(response.status_code, 302) # redirect somewhere class NoInlineDeletionTest(TestCase): - urls = "regressiontests.generic_inline_admin.urls" + urls = "generic_inline_admin.urls" def test_no_deletion(self): fake_site = object() @@ -234,7 +235,7 @@ request.user = MockSuperUser() class GenericInlineModelAdminTest(TestCase): - urls = "regressiontests.generic_inline_admin.urls" + urls = "generic_inline_admin.urls" def setUp(self): self.site = AdminSite() @@ -244,7 +245,7 @@ class GenericInlineModelAdminTest(TestCase): # Create a formset with default arguments formset = media_inline.get_formset(request) - self.assertEqual(formset.max_num, None) + self.assertEqual(formset.max_num, DEFAULT_MAX_NUM) self.assertEqual(formset.can_order, False) # Create a formset with custom keyword arguments diff --git a/tests/regressiontests/generic_inline_admin/urls.py b/tests/generic_inline_admin/urls.py index 88d7b574d4..88d7b574d4 100644 --- a/tests/regressiontests/generic_inline_admin/urls.py +++ b/tests/generic_inline_admin/urls.py diff --git a/tests/regressiontests/admin_filters/__init__.py b/tests/generic_relations/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_filters/__init__.py +++ b/tests/generic_relations/__init__.py diff --git a/tests/modeltests/generic_relations/models.py b/tests/generic_relations/models.py index 2f025e660b..34dc8d3a7d 100644 --- a/tests/modeltests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -88,9 +88,13 @@ class Mineral(models.Model): return self.name class GeckoManager(models.Manager): - def get_query_set(self): - return super(GeckoManager, self).get_query_set().filter(has_tail=True) + def get_queryset(self): + return super(GeckoManager, self).get_queryset().filter(has_tail=True) class Gecko(models.Model): has_tail = models.BooleanField() objects = GeckoManager() + +# To test fix for #11263 +class Rock(Mineral): + tags = generic.GenericRelation(TaggedItem) diff --git a/tests/modeltests/generic_relations/tests.py b/tests/generic_relations/tests.py index 73b0a483a2..27b25185ea 100644 --- a/tests/modeltests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.test import TestCase from .models import (TaggedItem, ValuableTaggedItem, Comparison, Animal, - Vegetable, Mineral, Gecko) + Vegetable, Mineral, Gecko, Rock) class GenericRelationsTests(TestCase): @@ -231,6 +231,16 @@ class GenericRelationsTests(TestCase): tag = TaggedItem.objects.create(content_object=tailless, tag="lizard") self.assertEqual(tag.content_object, tailless) + def test_subclasses_with_gen_rel(self): + """ + Test that concrete model subclasses with generic relations work + correctly (ticket 11263). + """ + granite = Rock.objects.create(name='granite', hardness=5) + TaggedItem.objects.create(content_object=granite, tag="countertop") + self.assertEqual(Rock.objects.filter(tags__tag="countertop").count(), 1) + + class CustomWidget(forms.TextInput): pass diff --git a/tests/regressiontests/admin_inlines/__init__.py b/tests/generic_relations_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_inlines/__init__.py +++ b/tests/generic_relations_regress/__init__.py diff --git a/tests/regressiontests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py index dacc9b380b..2795471f7f 100644 --- a/tests/regressiontests/generic_relations_regress/models.py +++ b/tests/generic_relations_regress/models.py @@ -6,7 +6,7 @@ from django.utils.encoding import python_2_unicode_compatible __all__ = ('Link', 'Place', 'Restaurant', 'Person', 'Address', 'CharLink', 'TextLink', 'OddRelation1', 'OddRelation2', - 'Contact', 'Organization', 'Note') + 'Contact', 'Organization', 'Note', 'Company') @python_2_unicode_compatible class Link(models.Model): @@ -84,3 +84,41 @@ class Organization(models.Model): name = models.CharField(max_length=255) contacts = models.ManyToManyField(Contact, related_name='organizations') +@python_2_unicode_compatible +class Company(models.Model): + name = models.CharField(max_length=100) + links = generic.GenericRelation(Link) + + def __str__(self): + return "Company: %s" % self.name + +# For testing #13085 fix, we also use Note model defined above +class Developer(models.Model): + name = models.CharField(max_length=15) + +@python_2_unicode_compatible +class Team(models.Model): + name = models.CharField(max_length=15) + members = models.ManyToManyField(Developer) + + def __str__(self): + return "%s team" % self.name + + def __len__(self): + return self.members.count() + +class Guild(models.Model): + name = models.CharField(max_length=15) + members = models.ManyToManyField(Developer) + + def __nonzero__(self): + return self.members.count() + +class Tag(models.Model): + content_type = models.ForeignKey(ContentType, related_name='g_r_r_tags') + object_id = models.CharField(max_length=15) + content_object = generic.GenericForeignKey() + label = models.CharField(max_length=15) + +class Board(models.Model): + name = models.CharField(primary_key=True, max_length=15) diff --git a/tests/regressiontests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index 4c0f024433..474113584a 100644 --- a/tests/regressiontests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -1,8 +1,10 @@ from django.db.models import Q -from django.test import TestCase +from django.db.utils import IntegrityError +from django.test import TestCase, skipIfDBFeature from .models import (Address, Place, Restaurant, Link, CharLink, TextLink, - Person, Contact, Note, Organization, OddRelation1, OddRelation2) + Person, Contact, Note, Organization, OddRelation1, OddRelation2, Company, + Developer, Team, Guild, Tag, Board) class GenericRelationTests(TestCase): @@ -72,5 +74,64 @@ class GenericRelationTests(TestCase): Q(notes__note__icontains=r'other note')) self.assertTrue(org_contact in qs) + def test_join_reuse(self): + qs = Person.objects.filter( + addresses__street='foo' + ).filter( + addresses__street='bar' + ) + self.assertEqual(str(qs.query).count('JOIN'), 2) + def test_generic_relation_ordering(self): + """ + Test that ordering over a generic relation does not include extraneous + duplicate results, nor excludes rows not participating in the relation. + """ + p1 = Place.objects.create(name="South Park") + p2 = Place.objects.create(name="The City") + c = Company.objects.create(name="Chubby's Intl.") + l1 = Link.objects.create(content_object=p1) + l2 = Link.objects.create(content_object=c) + + places = list(Place.objects.order_by('links__id')) + def count_places(place): + return len([p for p in places if p.id == place.id]) + + self.assertEqual(len(places), 2) + self.assertEqual(count_places(p1), 1) + self.assertEqual(count_places(p2), 1) + + def test_target_model_is_unsaved(self): + """Test related to #13085""" + # Fails with another, ORM-level error + dev1 = Developer(name='Joe') + note = Note(note='Deserves promotion', content_object=dev1) + self.assertRaises(IntegrityError, note.save) + + def test_target_model_len_zero(self): + """Test for #13085 -- __len__() returns 0""" + team1 = Team.objects.create(name='Backend devs') + try: + note = Note(note='Deserve a bonus', content_object=team1) + except Exception as e: + if issubclass(type(e), Exception) and str(e) == 'Impossible arguments to GFK.get_content_type!': + self.fail("Saving model with GenericForeignKey to model instance whose __len__ method returns 0 shouldn't fail.") + raise e + note.save() + + def test_target_model_nonzero_false(self): + """Test related to #13085""" + # __nonzero__() returns False -- This actually doesn't currently fail. + # This test validates that + g1 = Guild.objects.create(name='First guild') + note = Note(note='Note for guild', content_object=g1) + note.save() + @skipIfDBFeature('interprets_empty_strings_as_nulls') + def test_gfk_to_model_with_empty_pk(self): + """Test related to #13085""" + # Saving model with GenericForeignKey to model instance with an + # empty CharField PK + b1 = Board.objects.create(name='') + tag = Tag(label='VP', content_object=b1) + tag.save() diff --git a/tests/regressiontests/admin_ordering/__init__.py b/tests/generic_views/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_ordering/__init__.py +++ b/tests/generic_views/__init__.py diff --git a/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json b/tests/generic_views/fixtures/generic-views-test-data.json index dfffbbf8d8..dfffbbf8d8 100644 --- a/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json +++ b/tests/generic_views/fixtures/generic-views-test-data.json diff --git a/tests/regressiontests/generic_views/models.py b/tests/generic_views/models.py index 30c73d67ca..30c73d67ca 100644 --- a/tests/regressiontests/generic_views/models.py +++ b/tests/generic_views/models.py diff --git a/tests/regressiontests/generic_views/templates/generic_views/about.html b/tests/generic_views/templates/generic_views/about.html index f946f630cf..f946f630cf 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/about.html +++ b/tests/generic_views/templates/generic_views/about.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/apple_detail.html b/tests/generic_views/templates/generic_views/apple_detail.html index 4fd0bd933b..4fd0bd933b 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/apple_detail.html +++ b/tests/generic_views/templates/generic_views/apple_detail.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/artist_detail.html b/tests/generic_views/templates/generic_views/artist_detail.html index 9ea13fed83..9ea13fed83 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/artist_detail.html +++ b/tests/generic_views/templates/generic_views/artist_detail.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/artist_form.html b/tests/generic_views/templates/generic_views/artist_form.html index 114e9bc5cc..114e9bc5cc 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/artist_form.html +++ b/tests/generic_views/templates/generic_views/artist_form.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html b/tests/generic_views/templates/generic_views/author_confirm_delete.html index d6ed99846e..d6ed99846e 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html +++ b/tests/generic_views/templates/generic_views/author_confirm_delete.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_detail.html b/tests/generic_views/templates/generic_views/author_detail.html index b6a60fd492..b6a60fd492 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/author_detail.html +++ b/tests/generic_views/templates/generic_views/author_detail.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_form.html b/tests/generic_views/templates/generic_views/author_form.html index 114e9bc5cc..114e9bc5cc 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/author_form.html +++ b/tests/generic_views/templates/generic_views/author_form.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_list.html b/tests/generic_views/templates/generic_views/author_list.html index 42ba331802..42ba331802 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/author_list.html +++ b/tests/generic_views/templates/generic_views/author_list.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_objects.html b/tests/generic_views/templates/generic_views/author_objects.html index 42ba331802..42ba331802 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/author_objects.html +++ b/tests/generic_views/templates/generic_views/author_objects.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_view.html b/tests/generic_views/templates/generic_views/author_view.html index dcd9d6b3a6..dcd9d6b3a6 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/author_view.html +++ b/tests/generic_views/templates/generic_views/author_view.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive.html b/tests/generic_views/templates/generic_views/book_archive.html index 03f45a38c2..03f45a38c2 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_archive.html +++ b/tests/generic_views/templates/generic_views/book_archive.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html b/tests/generic_views/templates/generic_views/book_archive_day.html index 4a7b5027b5..4a7b5027b5 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html +++ b/tests/generic_views/templates/generic_views/book_archive_day.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html b/tests/generic_views/templates/generic_views/book_archive_month.html index 4e1e52b35c..4e1e52b35c 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html +++ b/tests/generic_views/templates/generic_views/book_archive_month.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html b/tests/generic_views/templates/generic_views/book_archive_week.html index 0ddcbc98a9..0ddcbc98a9 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html +++ b/tests/generic_views/templates/generic_views/book_archive_week.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html b/tests/generic_views/templates/generic_views/book_archive_year.html index fde198dcaf..fde198dcaf 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html +++ b/tests/generic_views/templates/generic_views/book_archive_year.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_detail.html b/tests/generic_views/templates/generic_views/book_detail.html index c40af70426..c40af70426 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_detail.html +++ b/tests/generic_views/templates/generic_views/book_detail.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_list.html b/tests/generic_views/templates/generic_views/book_list.html index 42ba331802..42ba331802 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/book_list.html +++ b/tests/generic_views/templates/generic_views/book_list.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html b/tests/generic_views/templates/generic_views/confirm_delete.html index 87288c4495..87288c4495 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html +++ b/tests/generic_views/templates/generic_views/confirm_delete.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/detail.html b/tests/generic_views/templates/generic_views/detail.html index 1b6b9d946b..1b6b9d946b 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/detail.html +++ b/tests/generic_views/templates/generic_views/detail.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/form.html b/tests/generic_views/templates/generic_views/form.html index 7249998643..7249998643 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/form.html +++ b/tests/generic_views/templates/generic_views/form.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/list.html b/tests/generic_views/templates/generic_views/list.html index 42ba331802..42ba331802 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/list.html +++ b/tests/generic_views/templates/generic_views/list.html diff --git a/tests/regressiontests/generic_views/templates/generic_views/page_template.html b/tests/generic_views/templates/generic_views/page_template.html index 88e20c8b50..88e20c8b50 100644 --- a/tests/regressiontests/generic_views/templates/generic_views/page_template.html +++ b/tests/generic_views/templates/generic_views/page_template.html diff --git a/tests/generic_views/templates/generic_views/robots.txt b/tests/generic_views/templates/generic_views/robots.txt new file mode 100644 index 0000000000..72158267ba --- /dev/null +++ b/tests/generic_views/templates/generic_views/robots.txt @@ -0,0 +1 @@ +User-Agent: * diff --git a/tests/regressiontests/generic_views/templates/registration/login.html b/tests/generic_views/templates/registration/login.html index cac78221e2..cac78221e2 100644 --- a/tests/regressiontests/generic_views/templates/registration/login.html +++ b/tests/generic_views/templates/registration/login.html diff --git a/tests/regressiontests/generic_views/base.py b/tests/generic_views/test_base.py index c7ad7a0deb..0e84e17132 100644 --- a/tests/regressiontests/generic_views/base.py +++ b/tests/generic_views/test_base.py @@ -229,7 +229,7 @@ class ViewTest(unittest.TestCase): class TemplateViewTest(TestCase): - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' rf = RequestFactory() @@ -312,6 +312,11 @@ class TemplateViewTest(TestCase): self.assertNotEqual(response.content, response2.content) + def test_content_type(self): + response = self.client.get('/template/content_type/') + self.assertEqual(response['Content-Type'], 'text/plain') + + class RedirectViewTest(unittest.TestCase): rf = RequestFactory() @@ -324,66 +329,66 @@ class RedirectViewTest(unittest.TestCase): "Default is a permanent redirect" response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_temporary_redirect(self): "Permanent redirects are an option" response = RedirectView.as_view(url='/bar/', permanent=False)(self.rf.get('/foo/')) self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_include_args(self): "GET arguments can be included in the redirected URL" response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') response = RedirectView.as_view(url='/bar/', query_string=True)(self.rf.get('/foo/?pork=spam')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/?pork=spam') + self.assertEqual(response.url, '/bar/?pork=spam') def test_include_urlencoded_args(self): "GET arguments can be URL-encoded when included in the redirected URL" response = RedirectView.as_view(url='/bar/', query_string=True)( self.rf.get('/foo/?unicode=%E2%9C%93')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/?unicode=%E2%9C%93') + self.assertEqual(response.url, '/bar/?unicode=%E2%9C%93') def test_parameter_substitution(self): "Redirection URLs can be parameterized" response = RedirectView.as_view(url='/bar/%(object_id)d/')(self.rf.get('/foo/42/'), object_id=42) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/42/') + self.assertEqual(response.url, '/bar/42/') def test_redirect_POST(self): "Default is a permanent redirect" response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_redirect_HEAD(self): "Default is a permanent redirect" response = RedirectView.as_view(url='/bar/')(self.rf.head('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_redirect_OPTIONS(self): "Default is a permanent redirect" response = RedirectView.as_view(url='/bar/')(self.rf.options('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_redirect_PUT(self): "Default is a permanent redirect" response = RedirectView.as_view(url='/bar/')(self.rf.put('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_redirect_DELETE(self): "Default is a permanent redirect" response = RedirectView.as_view(url='/bar/')(self.rf.delete('/foo/')) self.assertEqual(response.status_code, 301) - self.assertEqual(response['Location'], '/bar/') + self.assertEqual(response.url, '/bar/') def test_redirect_when_meta_contains_no_query_string(self): "regression for #16705" diff --git a/tests/regressiontests/generic_views/dates.py b/tests/generic_views/test_dates.py index 0c565daf9f..263326322e 100644 --- a/tests/regressiontests/generic_views/dates.py +++ b/tests/generic_views/test_dates.py @@ -4,7 +4,7 @@ import time import datetime from django.core.exceptions import ImproperlyConfigured -from django.test import TestCase +from django.test import TestCase, skipUnlessDBFeature from django.test.utils import override_settings from django.utils import timezone from django.utils.unittest import skipUnless @@ -33,7 +33,7 @@ def _make_books(n, base_date): class ArchiveIndexViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_archive_view(self): @@ -119,6 +119,7 @@ class ArchiveIndexViewTests(TestCase): self.assertEqual(res.status_code, 200) @requires_tz_support + @skipUnlessDBFeature('has_zoneinfo_database') @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi') def test_aware_datetime_archive_view(self): BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc)) @@ -135,12 +136,12 @@ class ArchiveIndexViewTests(TestCase): class YearArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_year_view(self): res = self.client.get('/dates/books/2008/') self.assertEqual(res.status_code, 200) - self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)]) + self.assertEqual(list(res.context['date_list']), [datetime.date(2008, 10, 1)]) self.assertEqual(res.context['year'], datetime.date(2008, 1, 1)) self.assertTemplateUsed(res, 'generic_views/book_archive_year.html') @@ -151,7 +152,7 @@ class YearArchiveViewTests(TestCase): def test_year_view_make_object_list(self): res = self.client.get('/dates/books/2006/make_object_list/') self.assertEqual(res.status_code, 200) - self.assertEqual(list(res.context['date_list']), [datetime.datetime(2006, 5, 1)]) + self.assertEqual(list(res.context['date_list']), [datetime.date(2006, 5, 1)]) self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate__year=2006))) self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2006))) self.assertTemplateUsed(res, 'generic_views/book_archive_year.html') @@ -181,7 +182,7 @@ class YearArchiveViewTests(TestCase): res = self.client.get('/dates/books/%s/allow_future/' % year) self.assertEqual(res.status_code, 200) - self.assertEqual(list(res.context['date_list']), [datetime.datetime(year, 1, 1)]) + self.assertEqual(list(res.context['date_list']), [datetime.date(year, 1, 1)]) def test_year_view_paginated(self): res = self.client.get('/dates/books/2006/paginated/') @@ -204,6 +205,7 @@ class YearArchiveViewTests(TestCase): res = self.client.get('/dates/booksignings/2008/') self.assertEqual(res.status_code, 200) + @skipUnlessDBFeature('has_zoneinfo_database') @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi') def test_aware_datetime_year_view(self): BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc)) @@ -219,13 +221,13 @@ class YearArchiveViewTests(TestCase): class MonthArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_month_view(self): res = self.client.get('/dates/books/2008/oct/') self.assertEqual(res.status_code, 200) self.assertTemplateUsed(res, 'generic_views/book_archive_month.html') - self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)]) + self.assertEqual(list(res.context['date_list']), [datetime.date(2008, 10, 1)]) self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1)))) self.assertEqual(res.context['month'], datetime.date(2008, 10, 1)) @@ -268,7 +270,7 @@ class MonthArchiveViewTests(TestCase): # allow_future = True, valid future month res = self.client.get('/dates/books/%s/allow_future/' % urlbit) self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'][0].date(), b.pubdate) + self.assertEqual(res.context['date_list'][0], b.pubdate) self.assertEqual(list(res.context['book_list']), [b]) self.assertEqual(res.context['month'], future) @@ -328,6 +330,7 @@ class MonthArchiveViewTests(TestCase): res = self.client.get('/dates/booksignings/2008/apr/') self.assertEqual(res.status_code, 200) + @skipUnlessDBFeature('has_zoneinfo_database') @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi') def test_aware_datetime_month_view(self): BookSigning.objects.create(event_date=datetime.datetime(2008, 2, 1, 12, 0, tzinfo=timezone.utc)) @@ -345,7 +348,7 @@ class MonthArchiveViewTests(TestCase): class WeekArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_week_view(self): res = self.client.get('/dates/books/2008/week/39/') @@ -442,7 +445,7 @@ class WeekArchiveViewTests(TestCase): class DayArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_day_view(self): res = self.client.get('/dates/books/2008/oct/01/') @@ -560,7 +563,7 @@ class DayArchiveViewTests(TestCase): class DateDetailViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_date_detail_by_pk(self): res = self.client.get('/dates/books/2008/oct/01/1/') diff --git a/tests/regressiontests/generic_views/detail.py b/tests/generic_views/test_detail.py index c8f1999a2a..faee05688e 100644 --- a/tests/regressiontests/generic_views/detail.py +++ b/tests/generic_views/test_detail.py @@ -9,7 +9,7 @@ from .models import Artist, Author, Page class DetailViewTest(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_simple_object(self): res = self.client.get('/detail/obj/') diff --git a/tests/regressiontests/generic_views/edit.py b/tests/generic_views/test_edit.py index 0f1eb3cca7..c54d632363 100644 --- a/tests/regressiontests/generic_views/edit.py +++ b/tests/generic_views/test_edit.py @@ -13,16 +13,16 @@ from .models import Artist, Author class FormMixinTests(TestCase): - def test_initial_data(self): - """ Test instance independence of initial data dict (see #16138) """ - initial_1 = FormMixin().get_initial() - initial_1['foo'] = 'bar' - initial_2 = FormMixin().get_initial() - self.assertNotEqual(initial_1, initial_2) + def test_initial_data(self): + """ Test instance independence of initial data dict (see #16138) """ + initial_1 = FormMixin().get_initial() + initial_1['foo'] = 'bar' + initial_2 = FormMixin().get_initial() + self.assertNotEqual(initial_1, initial_2) class BasicFormTests(TestCase): - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_post_data(self): res = self.client.post('/contact/', {'name': "Me", 'message': "Hello"}) @@ -35,7 +35,7 @@ class ModelFormMixinTests(TestCase): self.assertEqual(form_class._meta.model, Author) class CreateViewTests(TestCase): - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_create(self): res = self.client.get('/edit/authors/create/') @@ -114,7 +114,7 @@ class CreateViewTests(TestCase): class UpdateViewTests(TestCase): - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_update_post(self): a = Author.objects.create( @@ -252,7 +252,7 @@ class UpdateViewTests(TestCase): class DeleteViewTests(TestCase): - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_delete_by_post(self): a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) @@ -283,6 +283,13 @@ class DeleteViewTests(TestCase): self.assertRedirects(res, 'http://testserver/edit/authors/create/') self.assertQuerysetEqual(Author.objects.all(), []) + def test_delete_with_interpolated_redirect(self): + a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) + res = self.client.post('/edit/author/%d/delete/interpolate_redirect/' % a.pk) + self.assertEqual(res.status_code, 302) + self.assertRedirects(res, 'http://testserver/edit/authors/create/?deleted=%d' % a.pk) + self.assertQuerysetEqual(Author.objects.all(), []) + def test_delete_with_special_properties(self): a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) res = self.client.get('/edit/author/%d/delete/special/' % a.pk) diff --git a/tests/regressiontests/generic_views/forms.py b/tests/generic_views/test_forms.py index e036ad8afc..e036ad8afc 100644 --- a/tests/regressiontests/generic_views/forms.py +++ b/tests/generic_views/test_forms.py diff --git a/tests/regressiontests/generic_views/list.py b/tests/generic_views/test_list.py index be2721d775..cc4d2f5966 100644 --- a/tests/regressiontests/generic_views/list.py +++ b/tests/generic_views/test_list.py @@ -11,7 +11,7 @@ from .models import Author, Artist class ListViewTests(TestCase): fixtures = ['generic-views-test-data.json'] - urls = 'regressiontests.generic_views.urls' + urls = 'generic_views.urls' def test_items(self): res = self.client.get('/list/dict/') diff --git a/tests/generic_views/tests.py b/tests/generic_views/tests.py new file mode 100644 index 0000000000..244aa3c701 --- /dev/null +++ b/tests/generic_views/tests.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +from .test_base import (ViewTest, TemplateViewTest, RedirectViewTest, + GetContextDataTest) +from .test_dates import (ArchiveIndexViewTests, YearArchiveViewTests, + MonthArchiveViewTests, WeekArchiveViewTests, DayArchiveViewTests, + DateDetailViewTests) +from .test_detail import DetailViewTest +from .test_edit import (FormMixinTests, BasicFormTests, ModelFormMixinTests, + CreateViewTests, UpdateViewTests, DeleteViewTests) +from .test_list import ListViewTests diff --git a/tests/regressiontests/generic_views/urls.py b/tests/generic_views/urls.py index 34b37fa7c4..695b50279a 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/generic_views/urls.py @@ -20,6 +20,8 @@ urlpatterns = patterns('', TemplateView.as_view(template_name='generic_views/about.html')), (r'^template/custom/(?P<foo>\w+)/$', views.CustomTemplateView.as_view(template_name='generic_views/about.html')), + (r'^template/content_type/$', + TemplateView.as_view(template_name='generic_views/robots.txt', content_type='text/plain')), (r'^template/cached/(?P<foo>\w+)/$', cache_page(2.0)(TemplateView.as_view(template_name='generic_views/about.html'))), @@ -95,6 +97,8 @@ urlpatterns = patterns('', views.NaiveAuthorDelete.as_view()), (r'^edit/author/(?P<pk>\d+)/delete/redirect/$', views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/')), + (r'^edit/author/(?P<pk>\d+)/delete/interpolate_redirect/$', + views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/?deleted=%(id)s')), (r'^edit/author/(?P<pk>\d+)/delete/$', views.AuthorDelete.as_view()), (r'^edit/author/(?P<pk>\d+)/delete/special/$', diff --git a/tests/regressiontests/generic_views/views.py b/tests/generic_views/views.py index 71e78e82c7..69bfcb32dd 100644 --- a/tests/regressiontests/generic_views/views.py +++ b/tests/generic_views/views.py @@ -1,12 +1,13 @@ from __future__ import absolute_import from django.contrib.auth.decorators import login_required +from django.contrib.messages.views import SuccessMessageMixin from django.core.paginator import Paginator from django.core.urlresolvers import reverse, reverse_lazy from django.utils.decorators import method_decorator from django.views import generic -from .forms import AuthorForm, ContactForm +from .test_forms import AuthorForm, ContactForm from .models import Artist, Author, Book, Page, BookSigning diff --git a/tests/regressiontests/admin_registration/__init__.py b/tests/get_earliest_or_latest/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_registration/__init__.py +++ b/tests/get_earliest_or_latest/__init__.py diff --git a/tests/get_earliest_or_latest/models.py b/tests/get_earliest_or_latest/models.py new file mode 100644 index 0000000000..cd62cf0104 --- /dev/null +++ b/tests/get_earliest_or_latest/models.py @@ -0,0 +1,16 @@ +from django.db import models + + +class Article(models.Model): + headline = models.CharField(max_length=100) + pub_date = models.DateField() + expire_date = models.DateField() + + class Meta: + get_latest_by = 'pub_date' + + +class Person(models.Model): + name = models.CharField(max_length=30) + birthday = models.DateField() + # Note that this model doesn't have "get_latest_by" set. diff --git a/tests/get_earliest_or_latest/tests.py b/tests/get_earliest_or_latest/tests.py new file mode 100644 index 0000000000..6317a0974c --- /dev/null +++ b/tests/get_earliest_or_latest/tests.py @@ -0,0 +1,123 @@ +from __future__ import absolute_import + +from datetime import datetime + +from django.test import TestCase + +from .models import Article, Person + + +class EarliestOrLatestTests(TestCase): + """Tests for the earliest() and latest() objects methods""" + + def tearDown(self): + """Makes sure Article has a get_latest_by""" + if not Article._meta.get_latest_by: + Article._meta.get_latest_by = 'pub_date' + + def test_earliest(self): + # Because no Articles exist yet, earliest() raises ArticleDoesNotExist. + self.assertRaises(Article.DoesNotExist, Article.objects.earliest) + + a1 = Article.objects.create( + headline="Article 1", pub_date=datetime(2005, 7, 26), + expire_date=datetime(2005, 9, 1) + ) + a2 = Article.objects.create( + headline="Article 2", pub_date=datetime(2005, 7, 27), + expire_date=datetime(2005, 7, 28) + ) + a3 = Article.objects.create( + headline="Article 3", pub_date=datetime(2005, 7, 28), + expire_date=datetime(2005, 8, 27) + ) + a4 = Article.objects.create( + headline="Article 4", pub_date=datetime(2005, 7, 28), + expire_date=datetime(2005, 7, 30) + ) + + # Get the earliest Article. + self.assertEqual(Article.objects.earliest(), a1) + # Get the earliest Article that matches certain filters. + self.assertEqual( + Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).earliest(), + a2 + ) + + # Pass a custom field name to earliest() to change the field that's used + # to determine the earliest object. + self.assertEqual(Article.objects.earliest('expire_date'), a2) + self.assertEqual(Article.objects.filter( + pub_date__gt=datetime(2005, 7, 26)).earliest('expire_date'), a2) + + # Ensure that earliest() overrides any other ordering specified on the + # query. Refs #11283. + self.assertEqual(Article.objects.order_by('id').earliest(), a1) + + # Ensure that error is raised if the user forgot to add a get_latest_by + # in the Model.Meta + Article.objects.model._meta.get_latest_by = None + self.assertRaisesMessage( + AssertionError, + "earliest() and latest() require either a field_name parameter or " + "'get_latest_by' in the model", + lambda: Article.objects.earliest(), + ) + + def test_latest(self): + # Because no Articles exist yet, latest() raises ArticleDoesNotExist. + self.assertRaises(Article.DoesNotExist, Article.objects.latest) + + a1 = Article.objects.create( + headline="Article 1", pub_date=datetime(2005, 7, 26), + expire_date=datetime(2005, 9, 1) + ) + a2 = Article.objects.create( + headline="Article 2", pub_date=datetime(2005, 7, 27), + expire_date=datetime(2005, 7, 28) + ) + a3 = Article.objects.create( + headline="Article 3", pub_date=datetime(2005, 7, 27), + expire_date=datetime(2005, 8, 27) + ) + a4 = Article.objects.create( + headline="Article 4", pub_date=datetime(2005, 7, 28), + expire_date=datetime(2005, 7, 30) + ) + + # Get the latest Article. + self.assertEqual(Article.objects.latest(), a4) + # Get the latest Article that matches certain filters. + self.assertEqual( + Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(), + a1 + ) + + # Pass a custom field name to latest() to change the field that's used + # to determine the latest object. + self.assertEqual(Article.objects.latest('expire_date'), a1) + self.assertEqual( + Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'), + a3, + ) + + # Ensure that latest() overrides any other ordering specified on the query. Refs #11283. + self.assertEqual(Article.objects.order_by('id').latest(), a4) + + # Ensure that error is raised if the user forgot to add a get_latest_by + # in the Model.Meta + Article.objects.model._meta.get_latest_by = None + self.assertRaisesMessage( + AssertionError, + "earliest() and latest() require either a field_name parameter or " + "'get_latest_by' in the model", + lambda: Article.objects.latest(), + ) + + def test_latest_manual(self): + # You can still use latest() with a model that doesn't have + # "get_latest_by" set -- just pass in the field name manually. + p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1)) + p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3)) + self.assertRaises(AssertionError, Person.objects.latest) + self.assertEqual(Person.objects.latest("birthday"), p2) diff --git a/tests/regressiontests/admin_scripts/__init__.py b/tests/get_object_or_404/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/__init__.py +++ b/tests/get_object_or_404/__init__.py diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/get_object_or_404/models.py index bda060569e..bb9aa60383 100644 --- a/tests/modeltests/get_object_or_404/models.py +++ b/tests/get_object_or_404/models.py @@ -22,8 +22,8 @@ class Author(models.Model): return self.name class ArticleManager(models.Manager): - def get_query_set(self): - return super(ArticleManager, self).get_query_set().filter(authors__name__icontains='sir') + def get_queryset(self): + return super(ArticleManager, self).get_queryset().filter(authors__name__icontains='sir') @python_2_unicode_compatible class Article(models.Model): diff --git a/tests/modeltests/get_object_or_404/tests.py b/tests/get_object_or_404/tests.py index 3b234c6cd3..38ebeb4f8c 100644 --- a/tests/modeltests/get_object_or_404/tests.py +++ b/tests/get_object_or_404/tests.py @@ -53,7 +53,7 @@ class GetObjectOr404Tests(TestCase): get_object_or_404, Author.objects.all() ) - # Using an EmptyQuerySet raises a Http404 error. + # Using an empty QuerySet raises a Http404 error. self.assertRaises(Http404, get_object_or_404, Article.objects.none(), title__contains="Run" ) diff --git a/tests/regressiontests/admin_scripts/app_with_import/__init__.py b/tests/get_or_create/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/app_with_import/__init__.py +++ b/tests/get_or_create/__init__.py diff --git a/tests/modeltests/get_or_create/models.py b/tests/get_or_create/models.py index 678f5a401c..678f5a401c 100644 --- a/tests/modeltests/get_or_create/models.py +++ b/tests/get_or_create/models.py diff --git a/tests/modeltests/get_or_create/tests.py b/tests/get_or_create/tests.py index 1e300fbb4d..1e300fbb4d 100644 --- a/tests/modeltests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py diff --git a/tests/regressiontests/admin_scripts/broken_app/__init__.py b/tests/get_or_create_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/broken_app/__init__.py +++ b/tests/get_or_create_regress/__init__.py diff --git a/tests/regressiontests/get_or_create_regress/models.py b/tests/get_or_create_regress/models.py index 637bbf62af..637bbf62af 100644 --- a/tests/regressiontests/get_or_create_regress/models.py +++ b/tests/get_or_create_regress/models.py diff --git a/tests/regressiontests/get_or_create_regress/tests.py b/tests/get_or_create_regress/tests.py index 92c371b6f8..92c371b6f8 100644 --- a/tests/regressiontests/get_or_create_regress/tests.py +++ b/tests/get_or_create_regress/tests.py diff --git a/tests/regressiontests/admin_scripts/complex_app/__init__.py b/tests/handlers/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/complex_app/__init__.py +++ b/tests/handlers/__init__.py diff --git a/tests/regressiontests/handlers/models.py b/tests/handlers/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/handlers/models.py +++ b/tests/handlers/models.py diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py new file mode 100644 index 0000000000..3680eecdd2 --- /dev/null +++ b/tests/handlers/tests.py @@ -0,0 +1,91 @@ +from django.core.handlers.wsgi import WSGIHandler +from django.core.signals import request_started, request_finished +from django.db import close_old_connections, connection +from django.test import RequestFactory, TestCase, TransactionTestCase +from django.test.utils import override_settings + + +class HandlerTests(TestCase): + + def setUp(self): + request_started.disconnect(close_old_connections) + + def tearDown(self): + request_started.connect(close_old_connections) + + # Mangle settings so the handler will fail + @override_settings(MIDDLEWARE_CLASSES=42) + def test_lock_safety(self): + """ + Tests for bug #11193 (errors inside middleware shouldn't leave + the initLock locked). + """ + # Try running the handler, it will fail in load_middleware + handler = WSGIHandler() + self.assertEqual(handler.initLock.locked(), False) + with self.assertRaises(Exception): + handler(None, None) + self.assertEqual(handler.initLock.locked(), False) + + def test_bad_path_info(self): + """Tests for bug #15672 ('request' referenced before assignment)""" + environ = RequestFactory().get('/').environ + environ['PATH_INFO'] = '\xed' + handler = WSGIHandler() + response = handler(environ, lambda *a, **k: None) + self.assertEqual(response.status_code, 400) + + +class TransactionsPerRequestTests(TransactionTestCase): + urls = 'handlers.urls' + + def test_no_transaction(self): + response = self.client.get('/in_transaction/') + self.assertContains(response, 'False') + + def test_auto_transaction(self): + old_atomic_requests = connection.settings_dict['ATOMIC_REQUESTS'] + try: + connection.settings_dict['ATOMIC_REQUESTS'] = True + response = self.client.get('/in_transaction/') + finally: + connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests + self.assertContains(response, 'True') + + def test_no_auto_transaction(self): + old_atomic_requests = connection.settings_dict['ATOMIC_REQUESTS'] + try: + connection.settings_dict['ATOMIC_REQUESTS'] = True + response = self.client.get('/not_in_transaction/') + finally: + connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests + self.assertContains(response, 'False') + +class SignalsTests(TestCase): + urls = 'handlers.urls' + + def setUp(self): + self.signals = [] + request_started.connect(self.register_started) + request_finished.connect(self.register_finished) + + def tearDown(self): + request_started.disconnect(self.register_started) + request_finished.disconnect(self.register_finished) + + def register_started(self, **kwargs): + self.signals.append('started') + + def register_finished(self, **kwargs): + self.signals.append('finished') + + def test_request_signals(self): + response = self.client.get('/regular/') + self.assertEqual(self.signals, ['started', 'finished']) + self.assertEqual(response.content, b"regular content") + + def test_request_signals_streaming_response(self): + response = self.client.get('/streaming/') + self.assertEqual(self.signals, ['started']) + self.assertEqual(b''.join(response.streaming_content), b"streaming content") + self.assertEqual(self.signals, ['started', 'finished']) diff --git a/tests/handlers/urls.py b/tests/handlers/urls.py new file mode 100644 index 0000000000..29858055ab --- /dev/null +++ b/tests/handlers/urls.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals + +from django.conf.urls import patterns, url + +from . import views + +urlpatterns = patterns('', + url(r'^regular/$', views.regular), + url(r'^streaming/$', views.streaming), + url(r'^in_transaction/$', views.in_transaction), + url(r'^not_in_transaction/$', views.not_in_transaction), +) diff --git a/tests/handlers/views.py b/tests/handlers/views.py new file mode 100644 index 0000000000..22d9ea4c7d --- /dev/null +++ b/tests/handlers/views.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals + +from django.db import connection +from django.http import HttpResponse, StreamingHttpResponse + +def regular(request): + return HttpResponse(b"regular content") + +def streaming(request): + return StreamingHttpResponse([b"streaming", b" ", b"content"]) + +def in_transaction(request): + return HttpResponse(str(connection.in_atomic_block)) + +def not_in_transaction(request): + return HttpResponse(str(connection.in_atomic_block)) +not_in_transaction.transactions_per_request = False diff --git a/tests/regressiontests/admin_scripts/complex_app/admin/__init__.py b/tests/http_utils/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/complex_app/admin/__init__.py +++ b/tests/http_utils/__init__.py diff --git a/tests/regressiontests/http_utils/models.py b/tests/http_utils/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/http_utils/models.py +++ b/tests/http_utils/models.py diff --git a/tests/regressiontests/http_utils/tests.py b/tests/http_utils/tests.py index 7dfd24d721..7dfd24d721 100644 --- a/tests/regressiontests/http_utils/tests.py +++ b/tests/http_utils/tests.py diff --git a/tests/regressiontests/admin_scripts/custom_templates/app_template/__init__.py b/tests/httpwrappers/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/app_template/__init__.py +++ b/tests/httpwrappers/__init__.py diff --git a/tests/regressiontests/httpwrappers/abc.txt b/tests/httpwrappers/abc.txt index 6bac42b3ad..6bac42b3ad 100644 --- a/tests/regressiontests/httpwrappers/abc.txt +++ b/tests/httpwrappers/abc.txt diff --git a/tests/regressiontests/httpwrappers/models.py b/tests/httpwrappers/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/httpwrappers/models.py +++ b/tests/httpwrappers/models.py diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index a601e541c6..194232e92f 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -7,6 +7,8 @@ import pickle import warnings from django.core.exceptions import SuspiciousOperation +from django.core.signals import request_finished +from django.db import close_old_connections from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseNotAllowed, HttpResponseNotModified, StreamingHttpResponse, @@ -130,15 +132,14 @@ class QueryDictTests(unittest.TestCase): self.assertTrue(q.has_key('foo')) self.assertTrue('foo' in q) - self.assertEqual(sorted(list(six.iteritems(q))), - [('foo', 'another'), ('name', 'john')]) - self.assertEqual(sorted(list(six.iterlists(q))), - [('foo', ['bar', 'baz', 'another']), ('name', ['john'])]) - self.assertEqual(sorted(list(six.iterkeys(q))), - ['foo', 'name']) - self.assertEqual(sorted(list(six.itervalues(q))), - ['another', 'john']) - self.assertEqual(len(q), 2) + self.assertListEqual(sorted(list(six.iteritems(q))), + [('foo', 'another'), ('name', 'john')]) + self.assertListEqual(sorted(list(six.iterlists(q))), + [('foo', ['bar', 'baz', 'another']), ('name', ['john'])]) + self.assertListEqual(sorted(list(six.iterkeys(q))), + ['foo', 'name']) + self.assertListEqual(sorted(list(six.itervalues(q))), + ['another', 'john']) q.update({'foo': 'hello'}) self.assertEqual(q['foo'], 'hello') @@ -327,13 +328,13 @@ class HttpResponseTests(unittest.TestCase): r = HttpResponse() r.content = ['1', '2', 3, '\u079e'] with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always", PendingDeprecationWarning) + warnings.simplefilter("always", DeprecationWarning) my_iter = iter(r) - self.assertEqual(w[0].category, PendingDeprecationWarning) + self.assertEqual(w[0].category, DeprecationWarning) with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always", PendingDeprecationWarning) + warnings.simplefilter("always", DeprecationWarning) result = list(my_iter) - self.assertEqual(w[0].category, PendingDeprecationWarning) + self.assertEqual(w[0].category, DeprecationWarning) #'\xde\x9e' == unichr(1950).encode('utf-8') self.assertEqual(result, [b'1', b'2', b'3', b'\xde\x9e']) self.assertEqual(r.content, b'123\xde\x9e') @@ -361,7 +362,7 @@ class HttpResponseTests(unittest.TestCase): # XXX change this when the deprecation completes in HttpResponse r = HttpResponse(iter(['hello', 'world'])) with warnings.catch_warnings(): - warnings.simplefilter("ignore", PendingDeprecationWarning) + warnings.simplefilter("ignore", DeprecationWarning) self.assertEqual(b''.join(r), b'helloworld') self.assertEqual(r.content, b'') # not the expected result! @@ -411,6 +412,8 @@ class HttpResponseSubclassesTests(TestCase): content='The resource has temporarily moved', content_type='text/html') self.assertContains(response, 'The resource has temporarily moved', status_code=302) + # Test that url attribute is right + self.assertEqual(response.url, response['Location']) def test_not_modified(self): response = HttpResponseNotModified() @@ -483,6 +486,15 @@ class StreamingHttpResponseTests(TestCase): r.tell() class FileCloseTests(TestCase): + + def setUp(self): + # Disable the request_finished signal during this test + # to avoid interfering with the database connection. + request_finished.disconnect(close_old_connections) + + def tearDown(self): + request_finished.connect(close_old_connections) + def test_response(self): filename = os.path.join(os.path.dirname(upath(__file__)), 'abc.txt') @@ -498,7 +510,7 @@ class FileCloseTests(TestCase): r = HttpResponse(file1) self.assertFalse(file1.closed) with warnings.catch_warnings(): - warnings.simplefilter("ignore", PendingDeprecationWarning) + warnings.simplefilter("ignore", DeprecationWarning) list(r) self.assertFalse(file1.closed) r.close() @@ -589,3 +601,7 @@ class CookieTests(unittest.TestCase): c['name']['httponly'] = True self.assertTrue(c['name']['httponly']) + def test_load_dict(self): + c = SimpleCookie() + c.load({'name': 'val'}) + self.assertEqual(c['name'].value, 'val') diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/__init__.py b/tests/i18n/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/custom_templates/project_template/project_name/__init__.py +++ b/tests/i18n/__init__.py diff --git a/tests/regressiontests/i18n/commands/__init__.py b/tests/i18n/commands/__init__.py index ffd439140e..ffd439140e 100644 --- a/tests/regressiontests/i18n/commands/__init__.py +++ b/tests/i18n/commands/__init__.py diff --git a/tests/regressiontests/i18n/commands/code.sample b/tests/i18n/commands/code.sample index bbcb83164b..bbcb83164b 100644 --- a/tests/regressiontests/i18n/commands/code.sample +++ b/tests/i18n/commands/code.sample diff --git a/tests/regressiontests/i18n/commands/compilation.py b/tests/i18n/commands/compilation.py index 2944469110..12bb89ed08 100644 --- a/tests/regressiontests/i18n/commands/compilation.py +++ b/tests/i18n/commands/compilation.py @@ -1,22 +1,25 @@ import os from django.core.management import call_command, CommandError -from django.test import TestCase +from django.test import SimpleTestCase from django.test.utils import override_settings -from django.utils import translation, six +from django.utils import translation from django.utils._os import upath from django.utils.six import StringIO test_dir = os.path.abspath(os.path.dirname(upath(__file__))) -class MessageCompilationTests(TestCase): +class MessageCompilationTests(SimpleTestCase): def setUp(self): self._cwd = os.getcwd() + self.addCleanup(os.chdir, self._cwd) + os.chdir(test_dir) - def tearDown(self): - os.chdir(self._cwd) + def rmfile(self, filepath): + if os.path.exists(filepath): + os.remove(filepath) class PoFileTests(MessageCompilationTests): @@ -25,9 +28,8 @@ class PoFileTests(MessageCompilationTests): MO_FILE = 'locale/%s/LC_MESSAGES/django.mo' % LOCALE def test_bom_rejection(self): - os.chdir(test_dir) with self.assertRaises(CommandError) as cm: - call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) + call_command('compilemessages', locale=self.LOCALE, stdout=StringIO()) self.assertIn("file has a BOM (Byte Order Mark)", cm.exception.args[0]) self.assertFalse(os.path.exists(self.MO_FILE)) @@ -43,8 +45,7 @@ class PoFileContentsTests(MessageCompilationTests): self.addCleanup(os.unlink, os.path.join(test_dir, self.MO_FILE)) def test_percent_symbol_in_po_file(self): - os.chdir(test_dir) - call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) + call_command('compilemessages', locale=self.LOCALE, stdout=StringIO()) self.assertTrue(os.path.exists(self.MO_FILE)) @@ -55,11 +56,14 @@ class PercentRenderingTests(MessageCompilationTests): LOCALE='it' MO_FILE='locale/%s/LC_MESSAGES/django.mo' % LOCALE + def setUp(self): + super(PercentRenderingTests, self).setUp() + self.addCleanup(os.unlink, os.path.join(test_dir, self.MO_FILE)) + @override_settings(LOCALE_PATHS=(os.path.join(test_dir, 'locale'),)) def test_percent_symbol_escaping(self): from django.template import Template, Context - os.chdir(test_dir) - call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) + call_command('compilemessages', locale=self.LOCALE, stdout=StringIO()) with translation.override(self.LOCALE): t = Template('{% load i18n %}{% trans "Looks like a str fmt spec %% o but shouldn\'t be interpreted as such" %}') rendered = t.render(Context({})) @@ -68,3 +72,42 @@ class PercentRenderingTests(MessageCompilationTests): t = Template('{% load i18n %}{% trans "Completed 50%% of all the tasks" %}') rendered = t.render(Context({})) self.assertEqual(rendered, 'IT translation of Completed 50%% of all the tasks') + + +@override_settings(LOCALE_PATHS=(os.path.join(test_dir, 'locale'),)) +class MultipleLocaleCompilationTests(MessageCompilationTests): + MO_FILE_HR = None + MO_FILE_FR = None + + def setUp(self): + super(MultipleLocaleCompilationTests, self).setUp() + localedir = os.path.join(test_dir, 'locale') + self.MO_FILE_HR = os.path.join(localedir, 'hr/LC_MESSAGES/django.mo') + self.MO_FILE_FR = os.path.join(localedir, 'fr/LC_MESSAGES/django.mo') + self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_HR)) + self.addCleanup(self.rmfile, os.path.join(localedir, self.MO_FILE_FR)) + + def test_one_locale(self): + call_command('compilemessages', locale='hr', stdout=StringIO()) + + self.assertTrue(os.path.exists(self.MO_FILE_HR)) + + def test_multiple_locales(self): + call_command('compilemessages', locale=['hr', 'fr'], stdout=StringIO()) + + self.assertTrue(os.path.exists(self.MO_FILE_HR)) + self.assertTrue(os.path.exists(self.MO_FILE_FR)) + + +class CompilationErrorHandling(MessageCompilationTests): + + LOCALE='ja' + MO_FILE='locale/%s/LC_MESSAGES/django.mo' % LOCALE + + def setUp(self): + super(CompilationErrorHandling, self).setUp() + self.addCleanup(self.rmfile, os.path.join(test_dir, self.MO_FILE)) + + def test_error_reported_by_msgfmt(self): + with self.assertRaises(CommandError): + call_command('compilemessages', locale=self.LOCALE, stdout=StringIO()) diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/i18n/commands/extraction.py index aa5efe1967..80e4ee0110 100644 --- a/tests/regressiontests/i18n/commands/extraction.py +++ b/tests/i18n/commands/extraction.py @@ -4,17 +4,20 @@ from __future__ import unicode_literals import os import re import shutil +import warnings from django.core import management -from django.test import TestCase +from django.test import SimpleTestCase from django.utils.encoding import force_text from django.utils._os import upath +from django.utils import six from django.utils.six import StringIO +from django.utils.translation import TranslatorCommentWarning LOCALE='de' -class ExtractorTests(TestCase): +class ExtractorTests(SimpleTestCase): PO_FILE='locale/%s/LC_MESSAGES/django.po' % LOCALE @@ -107,25 +110,34 @@ class BasicExtractorTests(ExtractorTests): self.assertMsgId('I think that 100%% is more that 50%% of %(obj)s.', po_contents) self.assertMsgId("Blocktrans extraction shouldn't double escape this: %%, a=%(a)s", po_contents) + def test_force_en_us_locale(self): + """Value of locale-munging option used by the command is the right one""" + from django.core.management.commands.makemessages import Command + self.assertTrue(Command.leave_locale_alone) + def test_extraction_error(self): os.chdir(self.test_dir) - shutil.copyfile('./templates/template_with_error.tpl', './templates/template_with_error.html') - self.assertRaises(SyntaxError, management.call_command, 'makemessages', locale=LOCALE, verbosity=0) + self.assertRaises(SyntaxError, management.call_command, 'makemessages', locale=LOCALE, extensions=['tpl'], verbosity=0) with self.assertRaises(SyntaxError) as context_manager: - management.call_command('makemessages', locale=LOCALE, verbosity=0) - self.assertRegexpMatches(str(context_manager.exception), - r'Translation blocks must not include other block tags: blocktrans \(file templates[/\\]template_with_error\.html, line 3\)' + management.call_command('makemessages', locale=LOCALE, extensions=['tpl'], verbosity=0) + six.assertRegex(self, str(context_manager.exception), + r'Translation blocks must not include other block tags: blocktrans \(file templates[/\\]template_with_error\.tpl, line 3\)' ) - os.remove('./templates/template_with_error.html') # Check that the temporary file was cleaned up - self.assertFalse(os.path.exists('./templates/template_with_error.html.py')) + self.assertFalse(os.path.exists('./templates/template_with_error.tpl.py')) def test_extraction_warning(self): + """test xgettext warning about multiple bare interpolation placeholders""" os.chdir(self.test_dir) shutil.copyfile('./code.sample', './code_sample.py') stdout = StringIO() - management.call_command('makemessages', locale=LOCALE, stdout=stdout) - os.remove('./code_sample.py') + try: + management.call_command('makemessages', locale=LOCALE, stdout=stdout) + finally: + try: + os.remove('./code_sample.py') + except OSError: + pass self.assertIn("code_sample.py:4", force_text(stdout.getvalue())) def test_template_message_context_extractor(self): @@ -141,23 +153,23 @@ class BasicExtractorTests(ExtractorTests): po_contents = force_text(fp.read()) # {% trans %} self.assertTrue('msgctxt "Special trans context #1"' in po_contents) - self.assertTrue("Translatable literal #7a" in po_contents) + self.assertMsgId("Translatable literal #7a", po_contents) self.assertTrue('msgctxt "Special trans context #2"' in po_contents) - self.assertTrue("Translatable literal #7b" in po_contents) + self.assertMsgId("Translatable literal #7b", po_contents) self.assertTrue('msgctxt "Special trans context #3"' in po_contents) - self.assertTrue("Translatable literal #7c" in po_contents) + self.assertMsgId("Translatable literal #7c", po_contents) # {% blocktrans %} self.assertTrue('msgctxt "Special blocktrans context #1"' in po_contents) - self.assertTrue("Translatable literal #8a" in po_contents) + self.assertMsgId("Translatable literal #8a", po_contents) self.assertTrue('msgctxt "Special blocktrans context #2"' in po_contents) - self.assertTrue("Translatable literal #8b-singular" in po_contents) + self.assertMsgId("Translatable literal #8b-singular", po_contents) self.assertTrue("Translatable literal #8b-plural" in po_contents) self.assertTrue('msgctxt "Special blocktrans context #3"' in po_contents) - self.assertTrue("Translatable literal #8c-singular" in po_contents) + self.assertMsgId("Translatable literal #8c-singular", po_contents) self.assertTrue("Translatable literal #8c-plural" in po_contents) self.assertTrue('msgctxt "Special blocktrans context #4"' in po_contents) - self.assertTrue("Translatable literal #8d" in po_contents) + self.assertMsgId("Translatable literal #8d %(a)s", po_contents) def test_context_in_single_quotes(self): os.chdir(self.test_dir) @@ -173,6 +185,63 @@ class BasicExtractorTests(ExtractorTests): self.assertTrue('msgctxt "Special blocktrans context wrapped in double quotes"' in po_contents) self.assertTrue('msgctxt "Special blocktrans context wrapped in single quotes"' in po_contents) + def test_template_comments(self): + """Template comment tags on the same line of other constructs (#19552)""" + os.chdir(self.test_dir) + # Test detection/end user reporting of old, incorrect templates + # translator comments syntax + with warnings.catch_warnings(record=True) as ws: + warnings.simplefilter('always') + management.call_command('makemessages', locale=LOCALE, extensions=['thtml'], verbosity=0) + self.assertEqual(len(ws), 3) + for w in ws: + self.assertTrue(issubclass(w.category, TranslatorCommentWarning)) + six.assertRegex(self, str(ws[0].message), + r"The translator-targeted comment 'Translators: ignored i18n comment #1' \(file templates/comments.thtml, line 4\) was ignored, because it wasn't the last item on the line\." + ) + six.assertRegex(self, str(ws[1].message), + r"The translator-targeted comment 'Translators: ignored i18n comment #3' \(file templates/comments.thtml, line 6\) was ignored, because it wasn't the last item on the line\." + ) + six.assertRegex(self, str(ws[2].message), + r"The translator-targeted comment 'Translators: ignored i18n comment #4' \(file templates/comments.thtml, line 8\) was ignored, because it wasn't the last item on the line\." + ) + # Now test .po file contents + self.assertTrue(os.path.exists(self.PO_FILE)) + with open(self.PO_FILE, 'r') as fp: + po_contents = force_text(fp.read()) + + self.assertMsgId('Translatable literal #9a', po_contents) + self.assertFalse('ignored comment #1' in po_contents) + + self.assertFalse('Translators: ignored i18n comment #1' in po_contents) + self.assertMsgId("Translatable literal #9b", po_contents) + + self.assertFalse('ignored i18n comment #2' in po_contents) + self.assertFalse('ignored comment #2' in po_contents) + self.assertMsgId('Translatable literal #9c', po_contents) + + self.assertFalse('ignored comment #3' in po_contents) + self.assertFalse('ignored i18n comment #3' in po_contents) + self.assertMsgId('Translatable literal #9d', po_contents) + + self.assertFalse('ignored comment #4' in po_contents) + self.assertMsgId('Translatable literal #9e', po_contents) + self.assertFalse('ignored comment #5' in po_contents) + + self.assertFalse('ignored i18n comment #4' in po_contents) + self.assertMsgId('Translatable literal #9f', po_contents) + self.assertTrue('#. Translators: valid i18n comment #5' in po_contents) + + self.assertMsgId('Translatable literal #9g', po_contents) + self.assertTrue('#. Translators: valid i18n comment #6' in po_contents) + self.assertMsgId('Translatable literal #9h', po_contents) + self.assertTrue('#. Translators: valid i18n comment #7' in po_contents) + self.assertMsgId('Translatable literal #9i', po_contents) + + six.assertRegex(self, po_contents, r'#\..+Translators: valid i18n comment #8') + six.assertRegex(self, po_contents, r'#\..+Translators: valid i18n comment #9') + self.assertMsgId("Translatable literal #9j", po_contents) + class JavascriptExtractorTests(ExtractorTests): @@ -293,3 +362,64 @@ class NoLocationExtractorTests(ExtractorTests): with open(self.PO_FILE, 'r') as fp: po_contents = force_text(fp.read()) self.assertTrue('#: templates/test.html:55' in po_contents) + + +class KeepPotFileExtractorTests(ExtractorTests): + + POT_FILE='locale/django.pot' + + def setUp(self): + super(KeepPotFileExtractorTests, self).setUp() + + def tearDown(self): + super(KeepPotFileExtractorTests, self).tearDown() + os.chdir(self.test_dir) + try: + os.unlink(self.POT_FILE) + except OSError: + pass + os.chdir(self._cwd) + + def test_keep_pot_disabled_by_default(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale=LOCALE, verbosity=0) + self.assertFalse(os.path.exists(self.POT_FILE)) + + def test_keep_pot_explicitly_disabled(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale=LOCALE, verbosity=0, + keep_pot=False) + self.assertFalse(os.path.exists(self.POT_FILE)) + + def test_keep_pot_enabled(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale=LOCALE, verbosity=0, + keep_pot=True) + self.assertTrue(os.path.exists(self.POT_FILE)) + + +class MultipleLocaleExtractionTests(ExtractorTests): + PO_FILE_PT = 'locale/pt/LC_MESSAGES/django.po' + PO_FILE_DE = 'locale/de/LC_MESSAGES/django.po' + LOCALES = ['pt', 'de', 'ch'] + + def tearDown(self): + os.chdir(self.test_dir) + for locale in self.LOCALES: + try: + self._rmrf('locale/%s' % locale) + except OSError: + pass + os.chdir(self._cwd) + + def test_multiple_locales(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale=['pt','de'], verbosity=0) + self.assertTrue(os.path.exists(self.PO_FILE_PT)) + self.assertTrue(os.path.exists(self.PO_FILE_DE)) + + def test_comma_separated_locales(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale='pt,de,ch', verbosity=0) + self.assertTrue(os.path.exists(self.PO_FILE_PT)) + self.assertTrue(os.path.exists(self.PO_FILE_DE)) diff --git a/tests/regressiontests/i18n/commands/ignore_dir/ignored.html b/tests/i18n/commands/ignore_dir/ignored.html index 6a296787ef..6a296787ef 100644 --- a/tests/regressiontests/i18n/commands/ignore_dir/ignored.html +++ b/tests/i18n/commands/ignore_dir/ignored.html diff --git a/tests/regressiontests/i18n/commands/javascript.js b/tests/i18n/commands/javascript.js index fa059d70f4..fa059d70f4 100644 --- a/tests/regressiontests/i18n/commands/javascript.js +++ b/tests/i18n/commands/javascript.js diff --git a/tests/regressiontests/i18n/commands/locale/es_AR/LC_MESSAGES/django.po b/tests/i18n/commands/locale/es_AR/LC_MESSAGES/django.po index 7f74a4ef4a..7f74a4ef4a 100644 --- a/tests/regressiontests/i18n/commands/locale/es_AR/LC_MESSAGES/django.po +++ b/tests/i18n/commands/locale/es_AR/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/commands/locale/fr/LC_MESSAGES/django.po b/tests/i18n/commands/locale/fr/LC_MESSAGES/django.po index 26bf6f133c..26bf6f133c 100644 --- a/tests/regressiontests/i18n/commands/locale/fr/LC_MESSAGES/django.po +++ b/tests/i18n/commands/locale/fr/LC_MESSAGES/django.po diff --git a/tests/i18n/commands/locale/hr/LC_MESSAGES/django.po b/tests/i18n/commands/locale/hr/LC_MESSAGES/django.po new file mode 100644 index 0000000000..663ca0000f --- /dev/null +++ b/tests/i18n/commands/locale/hr/LC_MESSAGES/django.po @@ -0,0 +1,71 @@ +# 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. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-12-04 04:59-0600\n" +"PO-Revision-Date: 2013-01-16 22:53-0300\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: fr\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" + +#. Translators: Django template comment for translators +#: templates/test.html:9 +#, python-format +msgid "I think that 100%% is more that 50%% of anything." +msgstr "" + +#: templates/test.html:10 +#, python-format +msgid "I think that 100%% is more that 50%% of %(obj)s." +msgstr "" + +#: templates/test.html:70 +#, python-format +msgid "Literal with a percent symbol at the end %%" +msgstr "" + +#: templates/test.html:71 +#, python-format +msgid "Literal with a percent %% symbol in the middle" +msgstr "" + +#: templates/test.html:72 +#, python-format +msgid "Completed 50%% of all the tasks" +msgstr "" + +#: templates/test.html:73 +#, python-format +msgctxt "ctx0" +msgid "Completed 99%% of all the tasks" +msgstr "" + +#: templates/test.html:74 +#, python-format +msgid "Shouldn't double escape this sequence: %% (two percent signs)" +msgstr "" + +#: templates/test.html:75 +#, python-format +msgctxt "ctx1" +msgid "Shouldn't double escape this sequence %% either" +msgstr "" + +#: templates/test.html:76 +#, python-format +msgid "Looks like a str fmt spec %%s but shouldn't be interpreted as such" +msgstr "Translation of the above string" + +#: templates/test.html:77 +#, python-format +msgid "Looks like a str fmt spec %% o but shouldn't be interpreted as such" +msgstr "Translation contains %% for the above string" diff --git a/tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.po b/tests/i18n/commands/locale/it/LC_MESSAGES/django.po index 445199580d..445199580d 100644 --- a/tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.po +++ b/tests/i18n/commands/locale/it/LC_MESSAGES/django.po diff --git a/tests/i18n/commands/locale/ja/LC_MESSAGES/django.po b/tests/i18n/commands/locale/ja/LC_MESSAGES/django.po new file mode 100644 index 0000000000..08d6be1e63 --- /dev/null +++ b/tests/i18n/commands/locale/ja/LC_MESSAGES/django.po @@ -0,0 +1,21 @@ +# 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. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-12-04 04:59-0600\n" +"PO-Revision-Date: 2013-02-26 21:29-0300\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#, brainfuck-format +msgwhat!? "This is an invalid PO file. GNU msgfmt should reject it." diff --git a/tests/i18n/commands/templates/comments.thtml b/tests/i18n/commands/templates/comments.thtml new file mode 100644 index 0000000000..90eb5f1792 --- /dev/null +++ b/tests/i18n/commands/templates/comments.thtml @@ -0,0 +1,13 @@ +{% load i18n %} + +{# ignored comment #1 #}{% trans "Translatable literal #9a" %} +{# Translators: ignored i18n comment #1 #}{% trans "Translatable literal #9b" %} +{# Translators: ignored i18n comment #2 #}{# ignored comment #2 #}{% trans "Translatable literal #9c" %} +{# ignored comment #3 #}{# Translators: ignored i18n comment #3 #}{% trans "Translatable literal #9d" %} +{# ignored comment #4 #}{% trans "Translatable literal #9e" %}{# ignored comment #5 #} +{# Translators: ignored i18n comment #4 #}{% trans "Translatable literal #9f" %}{# Translators: valid i18n comment #5 #} +{% trans "Translatable literal #9g" %}{# Translators: valid i18n comment #6 #} +{# ignored comment #6 #}{% trans "Translatable literal #9h" %}{# Translators: valid i18n comment #7 #} +{% trans "Translatable literal #9i" %} +{# Translators: valid i18n comment #8 #}{# Translators: valid i18n comment #9 #} +{% trans "Translatable literal #9j" %} diff --git a/tests/regressiontests/i18n/commands/templates/template_with_error.tpl b/tests/i18n/commands/templates/template_with_error.tpl index c2b93bdd06..c2b93bdd06 100644 --- a/tests/regressiontests/i18n/commands/templates/template_with_error.tpl +++ b/tests/i18n/commands/templates/template_with_error.tpl diff --git a/tests/regressiontests/i18n/commands/templates/test.html b/tests/i18n/commands/templates/test.html index e7d7f3ca53..6cb4493ef6 100644 --- a/tests/regressiontests/i18n/commands/templates/test.html +++ b/tests/i18n/commands/templates/test.html @@ -81,4 +81,4 @@ continued here.{% endcomment %} {% trans "Translatable literal with context wrapped in single quotes" context 'Context wrapped in single quotes' as var %} {% trans "Translatable literal with context wrapped in double quotes" context "Context wrapped in double quotes" as var %} {% blocktrans context 'Special blocktrans context wrapped in single quotes' %}Translatable literal with context wrapped in single quotes{% endblocktrans %} -{% blocktrans context "Special blocktrans context wrapped in double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %}
\ No newline at end of file +{% blocktrans context "Special blocktrans context wrapped in double quotes" %}Translatable literal with context wrapped in double quotes{% endblocktrans %} diff --git a/tests/i18n/commands/tests.py b/tests/i18n/commands/tests.py new file mode 100644 index 0000000000..f9e3c20fff --- /dev/null +++ b/tests/i18n/commands/tests.py @@ -0,0 +1,24 @@ +import os +import re +from subprocess import Popen, PIPE + +from django.core.management.utils import find_command + +can_run_extraction_tests = False +can_run_compilation_tests = False + +# 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): + can_run_extraction_tests = True + del p + +if find_command('msgfmt'): + can_run_compilation_tests = True diff --git a/tests/regressiontests/admin_scripts/management/__init__.py b/tests/i18n/contenttypes/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/management/__init__.py +++ b/tests/i18n/contenttypes/__init__.py diff --git a/tests/regressiontests/i18n/contenttypes/locale/en/LC_MESSAGES/django.mo b/tests/i18n/contenttypes/locale/en/LC_MESSAGES/django.mo Binary files differindex 673033ab30..673033ab30 100644 --- a/tests/regressiontests/i18n/contenttypes/locale/en/LC_MESSAGES/django.mo +++ b/tests/i18n/contenttypes/locale/en/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/contenttypes/locale/en/LC_MESSAGES/django.po b/tests/i18n/contenttypes/locale/en/LC_MESSAGES/django.po index 2529ce6dfb..2529ce6dfb 100644 --- a/tests/regressiontests/i18n/contenttypes/locale/en/LC_MESSAGES/django.po +++ b/tests/i18n/contenttypes/locale/en/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.mo b/tests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.mo Binary files differindex c3083c90b5..c3083c90b5 100644 --- a/tests/regressiontests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.mo +++ b/tests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.po b/tests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.po index b4916c2570..b4916c2570 100644 --- a/tests/regressiontests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.po +++ b/tests/i18n/contenttypes/locale/fr/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/contenttypes/tests.py b/tests/i18n/contenttypes/tests.py index 5e8a9823e1..5e8a9823e1 100644 --- a/tests/regressiontests/i18n/contenttypes/tests.py +++ b/tests/i18n/contenttypes/tests.py diff --git a/tests/regressiontests/i18n/forms.py b/tests/i18n/forms.py index abb99f443a..abb99f443a 100644 --- a/tests/regressiontests/i18n/forms.py +++ b/tests/i18n/forms.py diff --git a/tests/regressiontests/i18n/models.py b/tests/i18n/models.py index 9f2c293303..9f2c293303 100644 --- a/tests/regressiontests/i18n/models.py +++ b/tests/i18n/models.py diff --git a/tests/regressiontests/admin_scripts/management/commands/__init__.py b/tests/i18n/other/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/management/commands/__init__.py +++ b/tests/i18n/other/__init__.py diff --git a/tests/regressiontests/admin_scripts/simple_app/__init__.py b/tests/i18n/other/locale/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_scripts/simple_app/__init__.py +++ b/tests/i18n/other/locale/__init__.py diff --git a/tests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/i18n/other/locale/de/LC_MESSAGES/django.mo Binary files differnew file mode 100644 index 0000000000..ef64b650ec --- /dev/null +++ b/tests/i18n/other/locale/de/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/i18n/other/locale/de/LC_MESSAGES/django.po index a471d3814b..822fa3796b 100644 --- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po +++ b/tests/i18n/other/locale/de/LC_MESSAGES/django.po @@ -41,6 +41,38 @@ msgid_plural "%d results" msgstr[0] "%d Resultat" msgstr[1] "%d Resultate" +#: models.py:11 +msgid "%d good result" +msgid_plural "%d good results" +msgstr[0] "%d gutes Resultat" +msgstr[1] "%d guten Resultate" + +#: models.py:11 +msgid "good result" +msgid_plural "good results" +msgstr[0] "gutes Resultat" +msgstr[1] "guten Resultate" + +#: models.py:11 +msgctxt "Exclamation" +msgid "%d good result" +msgid_plural "%d good results" +msgstr[0] "%d gutes Resultat!" +msgstr[1] "%d guten Resultate!" + +#: models.py:11 +msgid "Hi %(name)s, %(num)d good result" +msgid_plural "Hi %(name)s, %(num)d good results" +msgstr[0] "Hallo %(name)s, %(num)d gutes Resultat" +msgstr[1] "Hallo %(name)s, %(num)d guten Resultate" + +#: models.py:11 +msgctxt "Greeting" +msgid "Hi %(name)s, %(num)d good result" +msgid_plural "Hi %(name)s, %(num)d good results" +msgstr[0] "Willkommen %(name)s, %(num)d gutes Resultat" +msgstr[1] "Willkommen %(name)s, %(num)d guten Resultate" + #: models.py:13 #, python-format msgid "The result was %(percent)s%%" diff --git a/tests/regressiontests/admin_util/__init__.py b/tests/i18n/other/locale/de/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_util/__init__.py +++ b/tests/i18n/other/locale/de/__init__.py diff --git a/tests/regressiontests/i18n/other/locale/de/formats.py b/tests/i18n/other/locale/de/formats.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/other/locale/de/formats.py +++ b/tests/i18n/other/locale/de/formats.py diff --git a/tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.mo b/tests/i18n/other/locale/fr/LC_MESSAGES/django.mo Binary files differindex 478338bc88..478338bc88 100644 --- a/tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.mo +++ b/tests/i18n/other/locale/fr/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.po b/tests/i18n/other/locale/fr/LC_MESSAGES/django.po index dafb6139ae..dafb6139ae 100644 --- a/tests/regressiontests/i18n/other/locale/fr/LC_MESSAGES/django.po +++ b/tests/i18n/other/locale/fr/LC_MESSAGES/django.po diff --git a/tests/regressiontests/admin_validation/__init__.py b/tests/i18n/patterns/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_validation/__init__.py +++ b/tests/i18n/patterns/__init__.py diff --git a/tests/regressiontests/i18n/patterns/locale/en/LC_MESSAGES/django.mo b/tests/i18n/patterns/locale/en/LC_MESSAGES/django.mo Binary files differindex ec7644b504..ec7644b504 100644 --- a/tests/regressiontests/i18n/patterns/locale/en/LC_MESSAGES/django.mo +++ b/tests/i18n/patterns/locale/en/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/patterns/locale/en/LC_MESSAGES/django.po b/tests/i18n/patterns/locale/en/LC_MESSAGES/django.po index 9a14a80ceb..9a14a80ceb 100644 --- a/tests/regressiontests/i18n/patterns/locale/en/LC_MESSAGES/django.po +++ b/tests/i18n/patterns/locale/en/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/patterns/locale/nl/LC_MESSAGES/django.mo b/tests/i18n/patterns/locale/nl/LC_MESSAGES/django.mo Binary files differindex 86c847ecf2..86c847ecf2 100644 --- a/tests/regressiontests/i18n/patterns/locale/nl/LC_MESSAGES/django.mo +++ b/tests/i18n/patterns/locale/nl/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/patterns/locale/nl/LC_MESSAGES/django.po b/tests/i18n/patterns/locale/nl/LC_MESSAGES/django.po index fff4bfe394..fff4bfe394 100644 --- a/tests/regressiontests/i18n/patterns/locale/nl/LC_MESSAGES/django.po +++ b/tests/i18n/patterns/locale/nl/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.mo b/tests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.mo Binary files differindex 1d7b346c27..1d7b346c27 100644 --- a/tests/regressiontests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.mo +++ b/tests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.po b/tests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.po index fd3388e4b0..fd3388e4b0 100644 --- a/tests/regressiontests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.po +++ b/tests/i18n/patterns/locale/pt_BR/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/patterns/templates/404.html b/tests/i18n/patterns/templates/404.html index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/patterns/templates/404.html +++ b/tests/i18n/patterns/templates/404.html diff --git a/tests/regressiontests/i18n/patterns/templates/dummy.html b/tests/i18n/patterns/templates/dummy.html index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/patterns/templates/dummy.html +++ b/tests/i18n/patterns/templates/dummy.html diff --git a/tests/regressiontests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py index 358cdf65db..d334e4fe2d 100644 --- a/tests/regressiontests/i18n/patterns/tests.py +++ b/tests/i18n/patterns/tests.py @@ -19,7 +19,7 @@ from django.utils import translation TEMPLATE_DIRS=( os.path.join(os.path.dirname(upath(__file__)), 'templates'), ), - LANGUAGE_CODE='en', + LANGUAGE_CODE='en-us', LANGUAGES=( ('nl', 'Dutch'), ('en', 'English'), @@ -34,7 +34,7 @@ class URLTestCaseBase(TestCase): """ TestCase base-class for the URL tests. """ - urls = 'regressiontests.i18n.patterns.urls.default' + urls = 'i18n.patterns.urls.default' def setUp(self): # Make sure the cache is empty before we are doing our tests. @@ -61,13 +61,13 @@ class URLPrefixTests(URLTestCaseBase): with translation.override('nl'): self.assertEqual(reverse('prefixed'), '/nl/prefixed/') - @override_settings(ROOT_URLCONF='regressiontests.i18n.patterns.urls.wrong') + @override_settings(ROOT_URLCONF='i18n.patterns.urls.wrong') def test_invalid_prefix_use(self): self.assertRaises(ImproperlyConfigured, lambda: reverse('account:register')) class URLDisabledTests(URLTestCaseBase): - urls = 'regressiontests.i18n.patterns.urls.disabled' + urls = 'i18n.patterns.urls.disabled' @override_settings(USE_I18N=False) def test_prefixed_i18n_disabled(self): @@ -82,7 +82,7 @@ class PathUnusedTests(URLTestCaseBase): Check that if no i18n_patterns is used in root urlconfs, then no language activation happens based on url prefix. """ - urls = 'regressiontests.i18n.patterns.urls.path_unused' + urls = 'i18n.patterns.urls.path_unused' def test_no_lang_activate(self): response = self.client.get('/nl/foo/') @@ -115,6 +115,7 @@ class URLTranslationTests(URLTestCaseBase): with translation.override('nl'): self.assertEqual(reverse('users'), '/nl/gebruikers/') + self.assertEqual(reverse('prefixed_xml'), '/nl/prefixed.xml') with translation.override('pt-br'): self.assertEqual(reverse('users'), '/pt-br/usuarios/') @@ -170,6 +171,34 @@ class URLRedirectTests(URLTestCaseBase): response = self.client.get(response['location']) self.assertEqual(response.status_code, 200) + def test_pl_pl_redirect(self): + # language from outside of the supported LANGUAGES list + response = self.client.get('/account/register/', HTTP_ACCEPT_LANGUAGE='pl-pl') + self.assertRedirects(response, '/en/account/register/') + + response = self.client.get(response['location']) + self.assertEqual(response.status_code, 200) + + +class URLVaryAcceptLanguageTests(URLTestCaseBase): + """ + Tests that 'Accept-Language' is not added to the Vary header when using + prefixed URLs. + """ + def test_no_prefix_response(self): + response = self.client.get('/not-prefixed/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.get('Vary'), 'Accept-Language') + + def test_en_redirect(self): + response = self.client.get('/account/register/', HTTP_ACCEPT_LANGUAGE='en') + self.assertRedirects(response, '/en/account/register/') + self.assertFalse(response.get('Vary')) + + response = self.client.get(response['location']) + self.assertEqual(response.status_code, 200) + self.assertFalse(response.get('Vary')) + class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase): """ @@ -186,6 +215,9 @@ class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase): self.assertIn(('http://testserver/en/account/register/', 301), response.redirect_chain) self.assertRedirects(response, '/en/account/register/', 302) + response = self.client.get('/prefixed.xml', HTTP_ACCEPT_LANGUAGE='en', follow=True) + self.assertRedirects(response, '/en/prefixed.xml', 302) + class URLRedirectWithoutTrailingSlashSettingTests(URLTestCaseBase): """ diff --git a/tests/regressiontests/admin_views/__init__.py b/tests/i18n/patterns/urls/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_views/__init__.py +++ b/tests/i18n/patterns/urls/__init__.py diff --git a/tests/regressiontests/i18n/patterns/urls/default.py b/tests/i18n/patterns/urls/default.py index f117502753..0edaea48ad 100644 --- a/tests/regressiontests/i18n/patterns/urls/default.py +++ b/tests/i18n/patterns/urls/default.py @@ -14,6 +14,7 @@ urlpatterns = patterns('', urlpatterns += i18n_patterns('', url(r'^prefixed/$', view, name='prefixed'), + url(r'^prefixed\.xml$', view, name='prefixed_xml'), url(_(r'^users/$'), view, name='users'), - url(_(r'^account/'), include('regressiontests.i18n.patterns.urls.namespace', namespace='account')), + url(_(r'^account/'), include('i18n.patterns.urls.namespace', namespace='account')), ) diff --git a/tests/regressiontests/i18n/patterns/urls/disabled.py b/tests/i18n/patterns/urls/disabled.py index 60fd222024..60fd222024 100644 --- a/tests/regressiontests/i18n/patterns/urls/disabled.py +++ b/tests/i18n/patterns/urls/disabled.py diff --git a/tests/regressiontests/i18n/patterns/urls/namespace.py b/tests/i18n/patterns/urls/namespace.py index 3a2467b7b5..3a2467b7b5 100644 --- a/tests/regressiontests/i18n/patterns/urls/namespace.py +++ b/tests/i18n/patterns/urls/namespace.py diff --git a/tests/regressiontests/i18n/patterns/urls/path_unused.py b/tests/i18n/patterns/urls/path_unused.py index 1254d16c4e..1254d16c4e 100644 --- a/tests/regressiontests/i18n/patterns/urls/path_unused.py +++ b/tests/i18n/patterns/urls/path_unused.py diff --git a/tests/regressiontests/i18n/patterns/urls/wrong.py b/tests/i18n/patterns/urls/wrong.py index 22e9d535cc..deed258909 100644 --- a/tests/regressiontests/i18n/patterns/urls/wrong.py +++ b/tests/i18n/patterns/urls/wrong.py @@ -4,5 +4,5 @@ from django.utils.translation import ugettext_lazy as _ urlpatterns = i18n_patterns('', - url(_(r'^account/'), include('regressiontests.i18n.patterns.urls.wrong_namespace', namespace='account')), + url(_(r'^account/'), include('i18n.patterns.urls.wrong_namespace', namespace='account')), ) diff --git a/tests/regressiontests/i18n/patterns/urls/wrong_namespace.py b/tests/i18n/patterns/urls/wrong_namespace.py index 2f7105d637..2f7105d637 100644 --- a/tests/regressiontests/i18n/patterns/urls/wrong_namespace.py +++ b/tests/i18n/patterns/urls/wrong_namespace.py diff --git a/tests/regressiontests/admin_widgets/__init__.py b/tests/i18n/resolution/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/admin_widgets/__init__.py +++ b/tests/i18n/resolution/__init__.py diff --git a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo b/tests/i18n/resolution/locale/de/LC_MESSAGES/django.mo Binary files differindex 91c7bc5978..91c7bc5978 100644 --- a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo +++ b/tests/i18n/resolution/locale/de/LC_MESSAGES/django.mo diff --git a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po b/tests/i18n/resolution/locale/de/LC_MESSAGES/django.po index bbc511f0ad..bbc511f0ad 100644 --- a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po +++ b/tests/i18n/resolution/locale/de/LC_MESSAGES/django.po diff --git a/tests/regressiontests/i18n/resolution/models.py b/tests/i18n/resolution/models.py index 4287ca8617..4287ca8617 100644 --- a/tests/regressiontests/i18n/resolution/models.py +++ b/tests/i18n/resolution/models.py diff --git a/tests/regressiontests/i18n/tests.py b/tests/i18n/tests.py index dcc288e600..d26a201efa 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -15,34 +15,42 @@ from django.test.utils import override_settings from django.utils import translation from django.utils.formats import (get_format, date_format, time_format, localize, localize_input, iter_format_modules, get_format_modules, - number_format) + number_format, reset_format_cache, sanitize_separators) from django.utils.importlib import import_module from django.utils.numberformat import format as nformat from django.utils._os import upath from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText from django.utils import six from django.utils.six import PY3 -from django.utils.translation import (ugettext, ugettext_lazy, activate, - deactivate, gettext_lazy, pgettext, npgettext, to_locale, - get_language_info, get_language, get_language_from_request, trans_real) - +from django.utils.translation import (activate, deactivate, + get_language, get_language_from_request, get_language_info, + to_locale, trans_real, + gettext, gettext_lazy, + ugettext, ugettext_lazy, + ngettext, ngettext_lazy, + ungettext, ungettext_lazy, + pgettext, pgettext_lazy, + npgettext, npgettext_lazy) from .commands.tests import can_run_extraction_tests, can_run_compilation_tests if can_run_extraction_tests: from .commands.extraction import (ExtractorTests, BasicExtractorTests, JavascriptExtractorTests, IgnoredExtractorTests, SymlinkExtractorTests, CopyPluralFormsExtractorTests, NoWrapExtractorTests, - NoLocationExtractorTests) + NoLocationExtractorTests, KeepPotFileExtractorTests, + MultipleLocaleExtractionTests) if can_run_compilation_tests: from .commands.compilation import (PoFileTests, PoFileContentsTests, - PercentRenderingTests) + PercentRenderingTests, MultipleLocaleCompilationTests, + CompilationErrorHandling) from .contenttypes.tests import ContentTypeTests from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm from .models import Company, TestModel from .patterns.tests import (URLRedirectWithoutTrailingSlashTests, URLTranslationTests, URLDisabledTests, URLTagTests, URLTestCaseBase, URLRedirectWithoutTrailingSlashSettingTests, URLNamespaceTests, - URLPrefixTests, URLResponseTests, URLRedirectTests, PathUnusedTests) + URLPrefixTests, URLResponseTests, URLRedirectTests, PathUnusedTests, + URLVaryAcceptLanguageTests) here = os.path.dirname(os.path.abspath(upath(__file__))) @@ -95,6 +103,45 @@ class TranslationTests(TestCase): self.assertEqual(six.text_type(s2), "test") @override_settings(LOCALE_PATHS=extended_locale_paths) + def test_ungettext_lazy(self): + simple_with_format = ungettext_lazy('%d good result', '%d good results') + simple_str_with_format = ngettext_lazy(str('%d good result'), str('%d good results')) + simple_context_with_format = npgettext_lazy('Exclamation', '%d good result', '%d good results') + simple_without_format = ungettext_lazy('good result', 'good results') + with translation.override('de'): + self.assertEqual(simple_with_format % 1, '1 gutes Resultat') + self.assertEqual(simple_with_format % 4, '4 guten Resultate') + self.assertEqual(simple_str_with_format % 1, str('1 gutes Resultat')) + self.assertEqual(simple_str_with_format % 4, str('4 guten Resultate')) + self.assertEqual(simple_context_with_format % 1, '1 gutes Resultat!') + self.assertEqual(simple_context_with_format % 4, '4 guten Resultate!') + self.assertEqual(simple_without_format % 1, 'gutes Resultat') + self.assertEqual(simple_without_format % 4, 'guten Resultate') + + complex_nonlazy = ungettext_lazy('Hi %(name)s, %(num)d good result', 'Hi %(name)s, %(num)d good results', 4) + complex_deferred = ungettext_lazy('Hi %(name)s, %(num)d good result', 'Hi %(name)s, %(num)d good results', 'num') + complex_str_nonlazy = ngettext_lazy(str('Hi %(name)s, %(num)d good result'), str('Hi %(name)s, %(num)d good results'), 4) + complex_str_deferred = ngettext_lazy(str('Hi %(name)s, %(num)d good result'), str('Hi %(name)s, %(num)d good results'), 'num') + complex_context_nonlazy = npgettext_lazy('Greeting', 'Hi %(name)s, %(num)d good result', 'Hi %(name)s, %(num)d good results', 4) + complex_context_deferred = npgettext_lazy('Greeting', 'Hi %(name)s, %(num)d good result', 'Hi %(name)s, %(num)d good results', 'num') + with translation.override('de'): + self.assertEqual(complex_nonlazy % {'num': 4, 'name': 'Jim'}, 'Hallo Jim, 4 guten Resultate') + self.assertEqual(complex_deferred % {'name': 'Jim', 'num': 1}, 'Hallo Jim, 1 gutes Resultat') + self.assertEqual(complex_deferred % {'name': 'Jim', 'num': 5}, 'Hallo Jim, 5 guten Resultate') + with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'): + complex_deferred % {'name': 'Jim'} + self.assertEqual(complex_str_nonlazy % {'num': 4, 'name': 'Jim'}, str('Hallo Jim, 4 guten Resultate')) + self.assertEqual(complex_str_deferred % {'name': 'Jim', 'num': 1}, str('Hallo Jim, 1 gutes Resultat')) + self.assertEqual(complex_str_deferred % {'name': 'Jim', 'num': 5}, str('Hallo Jim, 5 guten Resultate')) + with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'): + complex_str_deferred % {'name': 'Jim'} + self.assertEqual(complex_context_nonlazy % {'num': 4, 'name': 'Jim'}, 'Willkommen Jim, 4 guten Resultate') + self.assertEqual(complex_context_deferred % {'name': 'Jim', 'num': 1}, 'Willkommen Jim, 1 gutes Resultat') + self.assertEqual(complex_context_deferred % {'name': 'Jim', 'num': 5}, 'Willkommen Jim, 5 guten Resultate') + with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'): + complex_context_deferred % {'name': 'Jim'} + + @override_settings(LOCALE_PATHS=extended_locale_paths) def test_pgettext(self): trans_real._active = local() trans_real._translations = {} @@ -292,14 +339,10 @@ class TranslationTests(TestCase): self.assertEqual(rendered, 'My other name is James.') +@override_settings(USE_L10N=True) 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) @@ -315,24 +358,16 @@ class FormattingTests(TestCase): '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 """ - with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False): + with self.settings(USE_THOUSAND_SEPARATOR=False): self.assertEqual('66666.66', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=',')) self.assertEqual('66666A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B')) self.assertEqual('66666', nformat(self.n, decimal_sep='X', decimal_pos=0, grouping=1, thousand_sep='Y')) - with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): + with self.settings(USE_THOUSAND_SEPARATOR=True): self.assertEqual('66,666.66', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=',')) self.assertEqual('6B6B6B6B6A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B')) self.assertEqual('-66666.6', nformat(-66666.666, decimal_sep='.', decimal_pos=1)) @@ -345,12 +380,12 @@ class FormattingTests(TestCase): self.assertEqual('31.12.2009 в 20:50', Template('{{ dt|date:"d.m.Y в H:i" }}').render(self.ctxt)) self.assertEqual('⌚ 10:15', Template('{{ t|time:"⌚ H:i" }}').render(self.ctxt)) + @override_settings(USE_L10N=False) def test_l10n_disabled(self): """ Catalan locale with format i18n disabled translations will be used, but not formats """ - settings.USE_L10N = False with translation.override('ca', deactivate=True): self.assertEqual('N j, Y', get_format('DATE_FORMAT')) self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) @@ -428,14 +463,14 @@ class FormattingTests(TestCase): fr_formats.THOUSAND_SEPARATOR = '' fr_formats.FIRST_DAY_OF_WEEK = 0 + reset_format_cache() with translation.override('fr'): - with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True, - THOUSAND_SEPARATOR='!'): + with self.settings(USE_THOUSAND_SEPARATOR=True, THOUSAND_SEPARATOR='!'): self.assertEqual('', get_format('THOUSAND_SEPARATOR')) # Even a second time (after the format has been cached)... self.assertEqual('', get_format('THOUSAND_SEPARATOR')) - with self.settings(USE_L10N=True, FIRST_DAY_OF_WEEK=1): + with self.settings(FIRST_DAY_OF_WEEK=1): self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) # Even a second time (after the format has been cached)... self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) @@ -445,7 +480,6 @@ class FormattingTests(TestCase): fr_formats.FIRST_DAY_OF_WEEK = backup_FIRST_DAY_OF_WEEK def test_l10n_enabled(self): - settings.USE_L10N = True # Catalan locale with translation.override('ca', deactivate=True): self.assertEqual('j \d\e F \d\e Y', get_format('DATE_FORMAT')) @@ -457,67 +491,70 @@ class FormattingTests(TestCase): self.assertEqual('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('66.666,666', localize(self.n)) - self.assertEqual('99.999,999', localize(self.f)) - self.assertEqual('10.000', localize(self.l)) - self.assertEqual('True', localize(True)) + with self.settings(USE_THOUSAND_SEPARATOR=True): + self.assertEqual('66.666,666', localize(self.n)) + self.assertEqual('99.999,999', localize(self.f)) + self.assertEqual('10.000', localize(self.l)) + self.assertEqual('True', localize(True)) - settings.USE_THOUSAND_SEPARATOR = False - self.assertEqual('66666,666', localize(self.n)) - self.assertEqual('99999,999', localize(self.f)) - self.assertEqual('10000', localize(self.l)) - self.assertEqual('31 de desembre de 2009', localize(self.d)) - self.assertEqual('31 de desembre de 2009 a les 20:50', localize(self.dt)) + with self.settings(USE_THOUSAND_SEPARATOR=False): + self.assertEqual('66666,666', localize(self.n)) + self.assertEqual('99999,999', localize(self.f)) + self.assertEqual('10000', localize(self.l)) + self.assertEqual('31 de desembre de 2009', localize(self.d)) + self.assertEqual('31 de desembre de 2009 a les 20:50', localize(self.dt)) - settings.USE_THOUSAND_SEPARATOR = True - self.assertEqual('66.666,666', Template('{{ n }}').render(self.ctxt)) - self.assertEqual('99.999,999', Template('{{ f }}').render(self.ctxt)) - self.assertEqual('10.000', Template('{{ l }}').render(self.ctxt)) + with self.settings(USE_THOUSAND_SEPARATOR=True): + self.assertEqual('66.666,666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual('99.999,999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual('10.000', Template('{{ l }}').render(self.ctxt)) - form3 = I18nForm({ - 'decimal_field': '66.666,666', - 'float_field': '99.999,999', - 'date_field': '31/12/2009', - 'datetime_field': '31/12/2009 20:50', - 'time_field': '20:50', - 'integer_field': '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']) + with self.settings(USE_THOUSAND_SEPARATOR=True): + form3 = I18nForm({ + 'decimal_field': '66.666,666', + 'float_field': '99.999,999', + 'date_field': '31/12/2009', + 'datetime_field': '31/12/2009 20:50', + 'time_field': '20:50', + 'integer_field': '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('66666,666', Template('{{ n }}').render(self.ctxt)) - self.assertEqual('99999,999', Template('{{ f }}').render(self.ctxt)) - self.assertEqual('31 de desembre de 2009', Template('{{ d }}').render(self.ctxt)) - self.assertEqual('31 de desembre de 2009 a les 20:50', Template('{{ dt }}').render(self.ctxt)) - self.assertEqual('66666,67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) - self.assertEqual('100000,0', Template('{{ f|floatformat }}').render(self.ctxt)) - self.assertEqual('10:15:48', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) - self.assertEqual('31/12/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) - self.assertEqual('31/12/2009 20:50', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) - self.assertEqual(date_format(datetime.datetime.now(), "DATE_FORMAT"), Template('{% now "DATE_FORMAT" %}').render(self.ctxt)) + with self.settings(USE_THOUSAND_SEPARATOR=False): + self.assertEqual('66666,666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual('99999,999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual('31 de desembre de 2009', Template('{{ d }}').render(self.ctxt)) + self.assertEqual('31 de desembre de 2009 a les 20:50', Template('{{ dt }}').render(self.ctxt)) + self.assertEqual('66666,67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) + self.assertEqual('100000,0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual('10:15:48', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) + self.assertEqual('31/12/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) + self.assertEqual('31/12/2009 20:50', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) + self.assertEqual(date_format(datetime.datetime.now(), "DATE_FORMAT"), + Template('{% now "DATE_FORMAT" %}').render(self.ctxt)) - form4 = I18nForm({ - 'decimal_field': '66666,666', - 'float_field': '99999,999', - 'date_field': '31/12/2009', - 'datetime_field': '31/12/2009 20:50', - 'time_field': '20:50', - 'integer_field': '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']) + with self.settings(USE_THOUSAND_SEPARATOR=False): + form4 = I18nForm({ + 'decimal_field': '66666,666', + 'float_field': '99999,999', + 'date_field': '31/12/2009', + 'datetime_field': '31/12/2009 20:50', + 'time_field': '20:50', + 'integer_field': '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': '12', @@ -548,32 +585,32 @@ class FormattingTests(TestCase): self.assertEqual('12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) self.assertEqual('No localizable', localize('No localizable')) - settings.USE_THOUSAND_SEPARATOR = True - self.assertEqual('66,666.666', localize(self.n)) - self.assertEqual('99,999.999', localize(self.f)) - self.assertEqual('10,000', localize(self.l)) + with self.settings(USE_THOUSAND_SEPARATOR=True): + self.assertEqual('66,666.666', localize(self.n)) + self.assertEqual('99,999.999', localize(self.f)) + self.assertEqual('10,000', localize(self.l)) - settings.USE_THOUSAND_SEPARATOR = False - self.assertEqual('66666.666', localize(self.n)) - self.assertEqual('99999.999', localize(self.f)) - self.assertEqual('10000', localize(self.l)) - self.assertEqual('Dec. 31, 2009', localize(self.d)) - self.assertEqual('Dec. 31, 2009, 8:50 p.m.', localize(self.dt)) + with self.settings(USE_THOUSAND_SEPARATOR=False): + self.assertEqual('66666.666', localize(self.n)) + self.assertEqual('99999.999', localize(self.f)) + self.assertEqual('10000', localize(self.l)) + self.assertEqual('Dec. 31, 2009', localize(self.d)) + self.assertEqual('Dec. 31, 2009, 8:50 p.m.', localize(self.dt)) - settings.USE_THOUSAND_SEPARATOR = True - self.assertEqual('66,666.666', Template('{{ n }}').render(self.ctxt)) - self.assertEqual('99,999.999', Template('{{ f }}').render(self.ctxt)) - self.assertEqual('10,000', Template('{{ l }}').render(self.ctxt)) + with self.settings(USE_THOUSAND_SEPARATOR=True): + self.assertEqual('66,666.666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual('99,999.999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual('10,000', Template('{{ l }}').render(self.ctxt)) - settings.USE_THOUSAND_SEPARATOR = False - self.assertEqual('66666.666', Template('{{ n }}').render(self.ctxt)) - self.assertEqual('99999.999', Template('{{ f }}').render(self.ctxt)) - self.assertEqual('Dec. 31, 2009', Template('{{ d }}').render(self.ctxt)) - self.assertEqual('Dec. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt)) - self.assertEqual('66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) - self.assertEqual('100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) - self.assertEqual('12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) - self.assertEqual('12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) + with self.settings(USE_THOUSAND_SEPARATOR=False): + self.assertEqual('66666.666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual('99999.999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual('Dec. 31, 2009', Template('{{ d }}').render(self.ctxt)) + self.assertEqual('Dec. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt)) + self.assertEqual('66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) + self.assertEqual('100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual('12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) + self.assertEqual('12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) form5 = I18nForm({ 'decimal_field': '66666.666', @@ -607,17 +644,17 @@ class FormattingTests(TestCase): """ Check if sublocales fall back to the main locale """ - with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): + with self.settings(USE_THOUSAND_SEPARATOR=True): with translation.override('de-at', deactivate=True): self.assertEqual('66.666,666', Template('{{ n }}').render(self.ctxt)) with translation.override('es-us', deactivate=True): - self.assertEqual('31 de diciembre de 2009', date_format(self.d)) + self.assertEqual('31 de Diciembre de 2009', date_format(self.d)) def test_localized_input(self): """ Tests if form input is correctly localized """ - settings.USE_L10N = True + self.maxDiff = 1200 with translation.override('de-at', deactivate=True): form6 = CompanyForm({ 'name': 'acme', @@ -634,18 +671,35 @@ class FormattingTests(TestCase): self.assertEqual(datetime.datetime(2009, 12, 31, 6, 0, 0), form6.cleaned_data['date_added']) with self.settings(USE_THOUSAND_SEPARATOR=True): # Checking for the localized "products_delivered" field - self.assertTrue('<input type="text" name="products_delivered" value="12.000" id="id_products_delivered" />' in form6.as_ul()) + self.assertInHTML('<input type="text" name="products_delivered" value="12.000" id="id_products_delivered" />', form6.as_ul()) + + def test_sanitize_separators(self): + """ + Tests django.utils.formats.sanitize_separators. + """ + # Non-strings are untouched + self.assertEqual(sanitize_separators(123), 123) + + with translation.override('ru', deactivate=True): + # Russian locale has non-breaking space (\xa0) as thousand separator + # Check that usual space is accepted too when sanitizing inputs + with self.settings(USE_THOUSAND_SEPARATOR=True): + self.assertEqual(sanitize_separators('1\xa0234\xa0567'), '1234567') + self.assertEqual(sanitize_separators('77\xa0777,777'), '77777.777') + self.assertEqual(sanitize_separators('12 345'), '12345') + self.assertEqual(sanitize_separators('77 777,777'), '77777.777') + with self.settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=False): + self.assertEqual(sanitize_separators('12\xa0345'), '12\xa0345') def test_iter_format_modules(self): """ Tests the iter_format_modules function. """ - settings.USE_L10N = True with translation.override('de-at', deactivate=True): de_format_mod = import_module('django.conf.locale.de.formats') self.assertEqual(list(iter_format_modules('de')), [de_format_mod]) - with self.settings(FORMAT_MODULE_PATH='regressiontests.i18n.other.locale'): - test_de_format_mod = import_module('regressiontests.i18n.other.locale.de.formats') + with self.settings(FORMAT_MODULE_PATH='i18n.other.locale'): + test_de_format_mod = import_module('i18n.other.locale.de.formats') self.assertEqual(list(iter_format_modules('de')), [test_de_format_mod, de_format_mod]) def test_iter_format_modules_stability(self): @@ -653,19 +707,16 @@ class FormattingTests(TestCase): Tests the iter_format_modules function always yields format modules in a stable and correct order in presence of both base ll and ll_CC formats. """ - settings.USE_L10N = True en_format_mod = import_module('django.conf.locale.en.formats') en_gb_format_mod = import_module('django.conf.locale.en_GB.formats') self.assertEqual(list(iter_format_modules('en-gb')), [en_gb_format_mod, en_format_mod]) def test_get_format_modules_lang(self): - with self.settings(USE_L10N=True): - with translation.override('de', deactivate=True): - self.assertEqual('.', get_format('DECIMAL_SEPARATOR', lang='en')) + with translation.override('de', deactivate=True): + self.assertEqual('.', get_format('DECIMAL_SEPARATOR', lang='en')) def test_get_format_modules_stability(self): - with self.settings(USE_L10N=True, - FORMAT_MODULE_PATH='regressiontests.i18n.other.locale'): + with self.settings(FORMAT_MODULE_PATH='i18n.other.locale'): with translation.override('de', deactivate=True): old = str("%r") % get_format_modules(reverse=True) new = str("%r") % get_format_modules(reverse=True) # second try @@ -860,7 +911,7 @@ class AppResolutionOrderI18NTests(ResolutionOrderI18NTests): def setUp(self): self.old_installed_apps = settings.INSTALLED_APPS - settings.INSTALLED_APPS = ['regressiontests.i18n.resolution'] + list(settings.INSTALLED_APPS) + settings.INSTALLED_APPS = ['i18n.resolution'] + list(settings.INSTALLED_APPS) super(AppResolutionOrderI18NTests, self).setUp() def tearDown(self): @@ -877,7 +928,7 @@ class LocalePathsResolutionOrderI18NTests(ResolutionOrderI18NTests): self.assertUgettext('Time', 'LOCALE_PATHS') def test_locale_paths_override_app_translation(self): - extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution'] + extended_apps = list(settings.INSTALLED_APPS) + ['i18n.resolution'] with self.settings(INSTALLED_APPS=extended_apps): self.assertUgettext('Time', 'LOCALE_PATHS') @@ -908,6 +959,19 @@ class TestLanguageInfo(TestCase): self.assertEqual(li['name'], 'German') self.assertEqual(li['bidi'], False) + def test_unknown_language_code(self): + six.assertRaisesRegex(self, KeyError, r"Unknown language code xx\.", get_language_info, 'xx') + + def test_unknown_only_country_code(self): + li = get_language_info('de-xx') + self.assertEqual(li['code'], 'de') + self.assertEqual(li['name_local'], 'Deutsch') + self.assertEqual(li['name'], 'German') + self.assertEqual(li['bidi'], False) + + def test_unknown_language_code_and_country_code(self): + six.assertRaisesRegex(self, KeyError, r"Unknown language code xx-xx and xx\.", get_language_info, 'xx-xx') + class MultipleLocaleActivationTests(TestCase): """ @@ -1033,3 +1097,26 @@ class MultipleLocaleActivationTests(TestCase): t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}") with translation.override('nl'): self.assertEqual(t.render(Context({})), 'Nee') + + +@override_settings( + USE_I18N=True, + LANGUAGES=( + ('en', 'English'), + ('fr', 'French'), + ), + MIDDLEWARE_CLASSES=( + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + ), +) +class LocaleMiddlewareTests(TestCase): + + urls = 'i18n.urls' + + def test_streaming_response(self): + # Regression test for #5241 + response = self.client.get('/fr/streaming/') + self.assertContains(response, "Oui/Non") + response = self.client.get('/en/streaming/') + self.assertContains(response, "Yes/No") diff --git a/tests/i18n/urls.py b/tests/i18n/urls.py new file mode 100644 index 0000000000..c118265dab --- /dev/null +++ b/tests/i18n/urls.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals + +from django.conf.urls.i18n import i18n_patterns +from django.http import StreamingHttpResponse +from django.utils.translation import ugettext_lazy as _ + +urlpatterns = i18n_patterns('', + (r'^streaming/$', lambda r: StreamingHttpResponse([_("Yes"), "/", _("No")])), +) diff --git a/tests/regressiontests/aggregation_regress/__init__.py b/tests/indexes/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/aggregation_regress/__init__.py +++ b/tests/indexes/__init__.py diff --git a/tests/indexes/models.py b/tests/indexes/models.py new file mode 100644 index 0000000000..e38eb005db --- /dev/null +++ b/tests/indexes/models.py @@ -0,0 +1,20 @@ +from django.db import connection +from django.db import models + + +class Article(models.Model): + headline = models.CharField(max_length=100) + pub_date = models.DateTimeField() + + class Meta: + index_together = [ + ["headline", "pub_date"], + ] + + +# Indexing a TextField on Oracle or MySQL results in index creation error. +if connection.vendor == 'postgresql': + class IndexedArticle(models.Model): + headline = models.CharField(max_length=100, db_index=True) + body = models.TextField(db_index=True) + slug = models.CharField(max_length=40, unique=True) diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py new file mode 100644 index 0000000000..672e35d286 --- /dev/null +++ b/tests/indexes/tests.py @@ -0,0 +1,27 @@ +from django.core.management.color import no_style +from django.db import connections, DEFAULT_DB_ALIAS +from django.test import TestCase +from django.utils.unittest import skipUnless + +from .models import Article + + +class IndexesTests(TestCase): + def test_index_together(self): + connection = connections[DEFAULT_DB_ALIAS] + index_sql = connection.creation.sql_indexes_for_model(Article, no_style()) + self.assertEqual(len(index_sql), 1) + + @skipUnless(connections[DEFAULT_DB_ALIAS].vendor == 'postgresql', + "This is a postgresql-specific issue") + def test_postgresql_text_indexes(self): + """Test creation of PostgreSQL-specific text indexes (#12234)""" + from .models import IndexedArticle + connection = connections[DEFAULT_DB_ALIAS] + index_sql = connection.creation.sql_indexes_for_model(IndexedArticle, no_style()) + self.assertEqual(len(index_sql), 5) + self.assertIn('("headline" varchar_pattern_ops)', index_sql[1]) + self.assertIn('("body" text_pattern_ops)', index_sql[3]) + # unique=True and db_index=True should only create the varchar-specific + # index (#19441). + self.assertIn('("slug" varchar_pattern_ops)', index_sql[4]) diff --git a/tests/regressiontests/app_loading/__init__.py b/tests/initial_sql_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/app_loading/__init__.py +++ b/tests/initial_sql_regress/__init__.py diff --git a/tests/regressiontests/initial_sql_regress/models.py b/tests/initial_sql_regress/models.py index 76de6d3bcb..76de6d3bcb 100644 --- a/tests/regressiontests/initial_sql_regress/models.py +++ b/tests/initial_sql_regress/models.py diff --git a/tests/regressiontests/initial_sql_regress/sql/simple.sql b/tests/initial_sql_regress/sql/simple.sql index d82f8381af..c3edb11c5a 100644 --- a/tests/regressiontests/initial_sql_regress/sql/simple.sql +++ b/tests/initial_sql_regress/sql/simple.sql @@ -7,5 +7,6 @@ INSERT INTO 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 ('"100%" of % are not placeholders'); INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending');
diff --git a/tests/initial_sql_regress/tests.py b/tests/initial_sql_regress/tests.py new file mode 100644 index 0000000000..bd23aab15d --- /dev/null +++ b/tests/initial_sql_regress/tests.py @@ -0,0 +1,47 @@ +from django.core.management.color import no_style +from django.core.management.sql import custom_sql_for_model +from django.db import connections, DEFAULT_DB_ALIAS +from django.test import TestCase +from django.test.utils import override_settings + +from .models import Simple + + +class InitialSQLTests(TestCase): + """ + 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. + """ + + def test_initial_sql(self): + """ + As pointed out by #14661, test data loaded by custom SQL + can't be relied upon; as a result, the test framework flushes the + data contents before every test. This test validates that this has + occurred. + """ + self.assertEqual(Simple.objects.count(), 0) + + def test_custom_sql(self): + """ + Simulate the custom SQL loading by syncdb. + """ + connection = connections[DEFAULT_DB_ALIAS] + custom_sql = custom_sql_for_model(Simple, no_style(), connection) + self.assertEqual(len(custom_sql), 9) + cursor = connection.cursor() + for sql in custom_sql: + cursor.execute(sql) + self.assertEqual(Simple.objects.count(), 9) + self.assertEqual( + Simple.objects.get(name__contains='placeholders').name, + '"100%" of % are not placeholders' + ) + + @override_settings(DEBUG=True) + def test_custom_sql_debug(self): + """ + Same test, ensure that CursorDebugWrapper doesn't alter sql loading + (#3485). + """ + self.test_custom_sql() diff --git a/tests/regressiontests/app_loading/not_installed/__init__.py b/tests/inline_formsets/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/app_loading/not_installed/__init__.py +++ b/tests/inline_formsets/__init__.py diff --git a/tests/regressiontests/inline_formsets/models.py b/tests/inline_formsets/models.py index 40c85fe739..40c85fe739 100644 --- a/tests/regressiontests/inline_formsets/models.py +++ b/tests/inline_formsets/models.py diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/inline_formsets/tests.py index e979fb4337..df682d34ef 100644 --- a/tests/regressiontests/inline_formsets/tests.py +++ b/tests/inline_formsets/tests.py @@ -124,7 +124,7 @@ class InlineFormsetFactoryTest(TestCase): to use for the inline formset, we should get an exception. """ six.assertRaisesRegex(self, Exception, - "<class 'regressiontests.inline_formsets.models.Child'> has more than 1 ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'>", + "<class 'inline_formsets.models.Child'> has more than 1 ForeignKey to <class 'inline_formsets.models.Parent'>", inlineformset_factory, Parent, Child ) @@ -134,7 +134,7 @@ class InlineFormsetFactoryTest(TestCase): to the parent model, we should get an exception. """ self.assertRaises(Exception, - "fk_name 'school' is not a ForeignKey to <class 'regressiontests.inline_formsets.models.Parent'>", + "fk_name 'school' is not a ForeignKey to <class 'inline_formsets.models.Parent'>", inlineformset_factory, Parent, Child, fk_name='school' ) @@ -144,7 +144,7 @@ class InlineFormsetFactoryTest(TestCase): exception. """ six.assertRaisesRegex(self, Exception, - "<class 'regressiontests.inline_formsets.models.Child'> has no field named 'test'", + "<class 'inline_formsets.models.Child'> has no field named 'test'", inlineformset_factory, Parent, Child, fk_name='test' ) diff --git a/tests/regressiontests/bash_completion/management/commands/__init__.py b/tests/inspectdb/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/bash_completion/management/commands/__init__.py +++ b/tests/inspectdb/__init__.py diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py new file mode 100644 index 0000000000..2862f7f3dc --- /dev/null +++ b/tests/inspectdb/models.py @@ -0,0 +1,60 @@ +# -*- encoding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models + + +class People(models.Model): + name = models.CharField(max_length=255) + parent = models.ForeignKey('self') + +class Message(models.Model): + from_field = models.ForeignKey(People, db_column='from_id') + +class PeopleData(models.Model): + people_pk = models.ForeignKey(People, primary_key=True) + ssn = models.CharField(max_length=11) + +class PeopleMoreData(models.Model): + people_unique = models.ForeignKey(People, unique=True) + license = models.CharField(max_length=255) + +class DigitsInColumnName(models.Model): + all_digits = models.CharField(max_length=11, db_column='123') + leading_digit = models.CharField(max_length=11, db_column='4extra') + leading_digits = models.CharField(max_length=11, db_column='45extra') + +class SpecialColumnName(models.Model): + field = models.IntegerField(db_column='field') + # Underscores + field_field_0 = models.IntegerField(db_column='Field_') + field_field_1 = models.IntegerField(db_column='Field__') + field_field_2 = models.IntegerField(db_column='__field') + # Other chars + prc_x = models.IntegerField(db_column='prc(%) x') + non_ascii = models.IntegerField(db_column='tamaño') + +class ColumnTypes(models.Model): + id = models.AutoField(primary_key=True) + big_int_field = models.BigIntegerField() + bool_field = models.BooleanField() + null_bool_field = models.NullBooleanField() + char_field = models.CharField(max_length=10) + comma_separated_int_field = models.CommaSeparatedIntegerField(max_length=99) + date_field = models.DateField() + date_time_field = models.DateTimeField() + decimal_field = models.DecimalField(max_digits=6, decimal_places=1) + email_field = models.EmailField() + file_field = models.FileField(upload_to="unused") + file_path_field = models.FilePathField() + float_field = models.FloatField() + int_field = models.IntegerField() + ip_address_field = models.IPAddressField() + gen_ip_adress_field = models.GenericIPAddressField(protocol="ipv4") + pos_int_field = models.PositiveIntegerField() + pos_small_int_field = models.PositiveSmallIntegerField() + slug_field = models.SlugField() + small_int_field = models.SmallIntegerField() + text_field = models.TextField() + time_field = models.TimeField() + url_field = models.URLField() diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py new file mode 100644 index 0000000000..79cbbfa3f2 --- /dev/null +++ b/tests/inspectdb/tests.py @@ -0,0 +1,164 @@ +# -*- encoding: utf-8 -*- +from __future__ import unicode_literals + +import re + +from django.core.management import call_command +from django.db import connection +from django.test import TestCase, skipUnlessDBFeature +from django.utils.unittest import expectedFailure +from django.utils.six import PY3, StringIO + +if connection.vendor == 'oracle': + expectedFailureOnOracle = expectedFailure +else: + expectedFailureOnOracle = lambda f: f + +class InspectDBTestCase(TestCase): + + def test_stealth_table_name_filter_option(self): + out = StringIO() + # Lets limit the introspection to tables created for models of this + # application + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_'), + stdout=out) + error_message = "inspectdb has examined a table that should have been filtered out." + # contrib.contenttypes is one of the apps always installed when running + # the Django test suite, check that one of its tables hasn't been + # inspected + self.assertNotIn("class DjangoContentType(models.Model):", out.getvalue(), msg=error_message) + + # Inspecting oracle DB doesn't produce correct results, see #19884 + @expectedFailureOnOracle + def test_field_types(self): + """Test introspection of various Django field types""" + out = StringIO() + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_columntypes'), + stdout=out) + output = out.getvalue() + def assertFieldType(name, definition): + out_def = re.search(r'^\s*%s = (models.*)$' % name, output, re.MULTILINE).groups()[0] + self.assertEqual(definition, out_def) + + assertFieldType('id', "models.IntegerField(primary_key=True)") + assertFieldType('big_int_field', "models.BigIntegerField()") + if connection.vendor == 'mysql': + # No native boolean type on MySQL + assertFieldType('bool_field', "models.IntegerField()") + assertFieldType('null_bool_field', "models.IntegerField(blank=True, null=True)") + else: + assertFieldType('bool_field', "models.BooleanField()") + assertFieldType('null_bool_field', "models.NullBooleanField()") + assertFieldType('char_field', "models.CharField(max_length=10)") + assertFieldType('comma_separated_int_field', "models.CharField(max_length=99)") + assertFieldType('date_field', "models.DateField()") + assertFieldType('date_time_field', "models.DateTimeField()") + if connection.vendor == 'sqlite': + # Guessed arguments, see #5014 + assertFieldType('decimal_field', "models.DecimalField(max_digits=10, decimal_places=5) " + "# max_digits and decimal_places have been guessed, as this database handles decimal fields as float") + else: + assertFieldType('decimal_field', "models.DecimalField(max_digits=6, decimal_places=1)") + assertFieldType('email_field', "models.CharField(max_length=75)") + assertFieldType('file_field', "models.CharField(max_length=100)") + assertFieldType('file_path_field', "models.CharField(max_length=100)") + assertFieldType('float_field', "models.FloatField()") + assertFieldType('int_field', "models.IntegerField()") + if connection.vendor == 'postgresql': + # Only PostgreSQL has a specific type + assertFieldType('ip_address_field', "models.GenericIPAddressField()") + assertFieldType('gen_ip_adress_field', "models.GenericIPAddressField()") + else: + assertFieldType('ip_address_field', "models.CharField(max_length=15)") + assertFieldType('gen_ip_adress_field', "models.CharField(max_length=39)") + if connection.vendor == 'sqlite': + assertFieldType('pos_int_field', "models.PositiveIntegerField()") + assertFieldType('pos_small_int_field', "models.PositiveSmallIntegerField()") + else: + # 'unsigned' property undetected on other backends + assertFieldType('pos_int_field', "models.IntegerField()") + if connection.vendor == 'postgresql': + assertFieldType('pos_small_int_field', "models.SmallIntegerField()") + else: + assertFieldType('pos_small_int_field', "models.IntegerField()") + assertFieldType('slug_field', "models.CharField(max_length=50)") + if connection.vendor in ('sqlite', 'postgresql'): + assertFieldType('small_int_field', "models.SmallIntegerField()") + else: + assertFieldType('small_int_field', "models.IntegerField()") + assertFieldType('text_field', "models.TextField()") + assertFieldType('time_field', "models.TimeField()") + assertFieldType('url_field', "models.CharField(max_length=200)") + + @skipUnlessDBFeature('can_introspect_foreign_keys') + def test_attribute_name_not_python_keyword(self): + out = StringIO() + # Lets limit the introspection to tables created for models of this + # application + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_'), + stdout=out) + output = out.getvalue() + error_message = "inspectdb generated an attribute name which is a python keyword" + # Recursive foreign keys should be set to 'self' + self.assertIn("parent = models.ForeignKey('self')", output) + self.assertNotIn("from = models.ForeignKey(InspectdbPeople)", output, msg=error_message) + # As InspectdbPeople model is defined after InspectdbMessage, it should be quoted + self.assertIn("from_field = models.ForeignKey('InspectdbPeople', db_column='from_id')", + output) + self.assertIn("people_pk = models.ForeignKey(InspectdbPeople, primary_key=True)", + output) + self.assertIn("people_unique = models.ForeignKey(InspectdbPeople, unique=True)", + output) + + def test_digits_column_name_introspection(self): + """Introspection of column names consist/start with digits (#16536/#17676)""" + out = StringIO() + # Lets limit the introspection to tables created for models of this + # application + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_'), + stdout=out) + output = out.getvalue() + error_message = "inspectdb generated a model field name which is a number" + self.assertNotIn(" 123 = models.CharField", output, msg=error_message) + self.assertIn("number_123 = models.CharField", output) + + error_message = "inspectdb generated a model field name which starts with a digit" + self.assertNotIn(" 4extra = models.CharField", output, msg=error_message) + self.assertIn("number_4extra = models.CharField", output) + + self.assertNotIn(" 45extra = models.CharField", output, msg=error_message) + self.assertIn("number_45extra = models.CharField", output) + + def test_special_column_name_introspection(self): + """ + Introspection of column names containing special characters, + unsuitable for Python identifiers + """ + out = StringIO() + call_command('inspectdb', stdout=out) + output = out.getvalue() + base_name = 'Field' if connection.vendor != 'oracle' else 'field' + self.assertIn("field = models.IntegerField()", output) + self.assertIn("field_field = models.IntegerField(db_column='%s_')" % base_name, output) + self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output) + self.assertIn("field_field_1 = models.IntegerField(db_column='__field')", output) + self.assertIn("prc_x = models.IntegerField(db_column='prc(%) x')", output) + if PY3: + # Python 3 allows non-ascii identifiers + self.assertIn("tamaño = models.IntegerField()", output) + else: + self.assertIn("tama_o = models.IntegerField(db_column='tama\\xf1o')", output) + + def test_managed_models(self): + """Test that by default the command generates models with `Meta.managed = False` (#14305)""" + out = StringIO() + call_command('inspectdb', + table_name_filter=lambda tn:tn.startswith('inspectdb_columntypes'), + stdout=out) + output = out.getvalue() + self.longMessage = False + self.assertIn(" managed = False", output, msg='inspectdb should generate unmanaged models.') diff --git a/tests/regressiontests/backends/__init__.py b/tests/introspection/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/backends/__init__.py +++ b/tests/introspection/__init__.py diff --git a/tests/regressiontests/introspection/models.py b/tests/introspection/models.py index 4de82e47e7..00c5ebea3c 100644 --- a/tests/regressiontests/introspection/models.py +++ b/tests/introspection/models.py @@ -10,6 +10,7 @@ class Reporter(models.Model): last_name = models.CharField(max_length=30) email = models.EmailField() facebook_user_id = models.BigIntegerField(null=True) + raw_data = models.BinaryField(null=True) class Meta: unique_together = ('first_name', 'last_name') @@ -23,6 +24,7 @@ class Article(models.Model): headline = models.CharField(max_length=100) pub_date = models.DateField() reporter = models.ForeignKey(Reporter) + response_to = models.ForeignKey('self', null=True) def __str__(self): return self.headline diff --git a/tests/regressiontests/introspection/tests.py b/tests/introspection/tests.py index 2df946d874..67e0266a66 100644 --- a/tests/regressiontests/introspection/tests.py +++ b/tests/introspection/tests.py @@ -1,10 +1,8 @@ from __future__ import absolute_import, unicode_literals -from functools import update_wrapper - from django.db import connection from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature -from django.utils import six, unittest +from django.utils import unittest from .models import Reporter, Article @@ -14,36 +12,7 @@ else: expectedFailureOnOracle = lambda f: f -# 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 - 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(six.with_metaclass(IgnoreNotimplementedError, TestCase)): +class IntrospectionTests(TestCase): def test_table_names(self): tl = connection.introspection.table_names() self.assertEqual(tl, sorted(tl)) @@ -91,9 +60,13 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase) def test_get_table_description_types(self): cursor = connection.cursor() desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table) + # The MySQL exception is due to the cursor.description returning the same constant for + # text and blob columns. TODO: use information_schema database to retrieve the proper + # field type on MySQL self.assertEqual( [datatype(r[1], r) for r in desc], - ['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField'] + ['IntegerField', 'CharField', 'CharField', 'CharField', + 'BigIntegerField', 'BinaryField' if connection.vendor != 'mysql' else 'TextField'] ) # The following test fails on Oracle due to #17202 (can't correctly @@ -116,7 +89,7 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase) desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table) self.assertEqual( [r[6] for r in desc], - [False, False, False, False, True] + [False, False, False, False, True, True] ) # Regression test for #9991 - 'real' types in postgres @@ -137,12 +110,17 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase) # 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)}) + self.assertEqual(relations, {3: (0, Reporter._meta.db_table), + 4: (0, Article._meta.db_table)}) + @skipUnlessDBFeature('can_introspect_foreign_keys') def test_get_key_columns(self): cursor = connection.cursor() key_columns = connection.introspection.get_key_columns(cursor, Article._meta.db_table) - self.assertEqual(key_columns, [('reporter_id', Reporter._meta.db_table, 'id')]) + self.assertEqual( + set(key_columns), + set([('reporter_id', Reporter._meta.db_table, 'id'), + ('response_to_id', Article._meta.db_table, 'id')])) def test_get_primary_key_column(self): cursor = connection.cursor() diff --git a/tests/regressiontests/delete_regress/__init__.py b/tests/invalid_models/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/delete_regress/__init__.py +++ b/tests/invalid_models/__init__.py diff --git a/tests/regressiontests/bug639/__init__.py b/tests/invalid_models/invalid_models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/bug639/__init__.py +++ b/tests/invalid_models/invalid_models/__init__.py diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/invalid_models/invalid_models/models.py index 3c21e1ddb8..3c21e1ddb8 100644 --- a/tests/modeltests/invalid_models/invalid_models/models.py +++ b/tests/invalid_models/invalid_models/models.py diff --git a/tests/regressiontests/localflavor/models.py b/tests/invalid_models/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/localflavor/models.py +++ b/tests/invalid_models/models.py diff --git a/tests/modeltests/invalid_models/tests.py b/tests/invalid_models/tests.py index 5f6224c45d..f878525b3b 100644 --- a/tests/modeltests/invalid_models/tests.py +++ b/tests/invalid_models/tests.py @@ -43,7 +43,7 @@ class InvalidModelTestCase(unittest.TestCase): ) def test_invalid_models(self): try: - module = load_app("modeltests.invalid_models.invalid_models") + module = load_app("invalid_models.invalid_models") except Exception: self.fail('Unable to load invalid model module') diff --git a/tests/regressiontests/bug8245/__init__.py b/tests/known_related_objects/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/bug8245/__init__.py +++ b/tests/known_related_objects/__init__.py diff --git a/tests/modeltests/known_related_objects/fixtures/tournament.json b/tests/known_related_objects/fixtures/tournament.json index 2f2b1c5627..bd4cbc17a7 100644 --- a/tests/modeltests/known_related_objects/fixtures/tournament.json +++ b/tests/known_related_objects/fixtures/tournament.json @@ -4,62 +4,73 @@ "model": "known_related_objects.tournament", "fields": { "name": "Tourney 1" - } - }, + } + }, { "pk": 2, "model": "known_related_objects.tournament", "fields": { "name": "Tourney 2" - } - }, + } + }, + { + "pk": 1, + "model": "known_related_objects.organiser", + "fields": { + "name": "Organiser 1" + } + }, { "pk": 1, "model": "known_related_objects.pool", "fields": { "tournament": 1, + "organiser": 1, "name": "T1 Pool 1" - } - }, + } + }, { "pk": 2, "model": "known_related_objects.pool", "fields": { "tournament": 1, + "organiser": 1, "name": "T1 Pool 2" - } - }, + } + }, { "pk": 3, "model": "known_related_objects.pool", "fields": { "tournament": 2, + "organiser": 1, "name": "T2 Pool 1" - } - }, + } + }, { "pk": 4, "model": "known_related_objects.pool", "fields": { "tournament": 2, + "organiser": 1, "name": "T2 Pool 2" - } - }, + } + }, { "pk": 1, "model": "known_related_objects.poolstyle", "fields": { "name": "T1 Pool 2 Style", "pool": 2 - } - }, + } + }, { "pk": 2, "model": "known_related_objects.poolstyle", "fields": { "name": "T2 Pool 1 Style", "pool": 3 - } } + } ] diff --git a/tests/modeltests/known_related_objects/models.py b/tests/known_related_objects/models.py index 4c516dd7e8..e256cc38f2 100644 --- a/tests/modeltests/known_related_objects/models.py +++ b/tests/known_related_objects/models.py @@ -9,9 +9,13 @@ from django.db import models class Tournament(models.Model): name = models.CharField(max_length=30) +class Organiser(models.Model): + name = models.CharField(max_length=30) + class Pool(models.Model): name = models.CharField(max_length=30) tournament = models.ForeignKey(Tournament) + organiser = models.ForeignKey(Organiser) class PoolStyle(models.Model): name = models.CharField(max_length=30) diff --git a/tests/modeltests/known_related_objects/tests.py b/tests/known_related_objects/tests.py index 24feab2241..d28d266557 100644 --- a/tests/modeltests/known_related_objects/tests.py +++ b/tests/known_related_objects/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from django.test import TestCase -from .models import Tournament, Pool, PoolStyle +from .models import Tournament, Organiser, Pool, PoolStyle class ExistingRelatedInstancesTests(TestCase): fixtures = ['tournament.json'] @@ -21,12 +21,52 @@ class ExistingRelatedInstancesTests(TestCase): def test_foreign_key_multiple_prefetch(self): with self.assertNumQueries(2): - tournaments = list(Tournament.objects.prefetch_related('pool_set')) + tournaments = list(Tournament.objects.prefetch_related('pool_set').order_by('pk')) pool1 = tournaments[0].pool_set.all()[0] self.assertIs(tournaments[0], pool1.tournament) pool2 = tournaments[1].pool_set.all()[0] self.assertIs(tournaments[1], pool2.tournament) + def test_queryset_or(self): + tournament_1 = Tournament.objects.get(pk=1) + tournament_2 = Tournament.objects.get(pk=2) + with self.assertNumQueries(1): + pools = tournament_1.pool_set.all() | tournament_2.pool_set.all() + related_objects = set(pool.tournament for pool in pools) + self.assertEqual(related_objects, set((tournament_1, tournament_2))) + + def test_queryset_or_different_cached_items(self): + tournament = Tournament.objects.get(pk=1) + organiser = Organiser.objects.get(pk=1) + with self.assertNumQueries(1): + pools = tournament.pool_set.all() | organiser.pool_set.all() + first = pools.filter(pk=1)[0] + self.assertIs(first.tournament, tournament) + self.assertIs(first.organiser, organiser) + + def test_queryset_or_only_one_with_precache(self): + tournament_1 = Tournament.objects.get(pk=1) + tournament_2 = Tournament.objects.get(pk=2) + # 2 queries here as pool id 3 has tournament 2, which is not cached + with self.assertNumQueries(2): + pools = tournament_1.pool_set.all() | Pool.objects.filter(pk=3) + related_objects = set(pool.tournament for pool in pools) + self.assertEqual(related_objects, set((tournament_1, tournament_2))) + # and the other direction + with self.assertNumQueries(2): + pools = Pool.objects.filter(pk=3) | tournament_1.pool_set.all() + related_objects = set(pool.tournament for pool in pools) + self.assertEqual(related_objects, set((tournament_1, tournament_2))) + + def test_queryset_and(self): + tournament = Tournament.objects.get(pk=1) + organiser = Organiser.objects.get(pk=1) + with self.assertNumQueries(1): + pools = tournament.pool_set.all() & organiser.pool_set.all() + first = pools.filter(pk=1)[0] + self.assertIs(first.tournament, tournament) + self.assertIs(first.organiser, organiser) + def test_one_to_one(self): with self.assertNumQueries(2): style = PoolStyle.objects.get(pk=1) @@ -41,7 +81,7 @@ class ExistingRelatedInstancesTests(TestCase): def test_one_to_one_multi_select_related(self): with self.assertNumQueries(1): - poolstyles = list(PoolStyle.objects.select_related('pool')) + poolstyles = list(PoolStyle.objects.select_related('pool').order_by('pk')) self.assertIs(poolstyles[0], poolstyles[0].pool.poolstyle) self.assertIs(poolstyles[1], poolstyles[1].pool.poolstyle) @@ -53,7 +93,7 @@ class ExistingRelatedInstancesTests(TestCase): def test_one_to_one_multi_prefetch_related(self): with self.assertNumQueries(2): - poolstyles = list(PoolStyle.objects.prefetch_related('pool')) + poolstyles = list(PoolStyle.objects.prefetch_related('pool').order_by('pk')) self.assertIs(poolstyles[0], poolstyles[0].pool.poolstyle) self.assertIs(poolstyles[1], poolstyles[1].pool.poolstyle) @@ -77,12 +117,12 @@ class ExistingRelatedInstancesTests(TestCase): def test_reverse_one_to_one_multi_select_related(self): with self.assertNumQueries(1): - pools = list(Pool.objects.select_related('poolstyle')) + pools = list(Pool.objects.select_related('poolstyle').order_by('pk')) self.assertIs(pools[1], pools[1].poolstyle.pool) self.assertIs(pools[2], pools[2].poolstyle.pool) def test_reverse_one_to_one_multi_prefetch_related(self): with self.assertNumQueries(2): - pools = list(Pool.objects.prefetch_related('poolstyle')) + pools = list(Pool.objects.prefetch_related('poolstyle').order_by('pk')) self.assertIs(pools[1], pools[1].poolstyle.pool) self.assertIs(pools[2], pools[2].poolstyle.pool) diff --git a/tests/regressiontests/builtin_server/__init__.py b/tests/logging_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/builtin_server/__init__.py +++ b/tests/logging_tests/__init__.py diff --git a/tests/regressiontests/logging_tests/logconfig.py b/tests/logging_tests/logconfig.py index b068103eb9..b068103eb9 100644 --- a/tests/regressiontests/logging_tests/logconfig.py +++ b/tests/logging_tests/logconfig.py diff --git a/tests/regressiontests/logging_tests/models.py b/tests/logging_tests/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/logging_tests/models.py +++ b/tests/logging_tests/models.py diff --git a/tests/regressiontests/logging_tests/tests.py b/tests/logging_tests/tests.py index 07804eb398..27ae651042 100644 --- a/tests/regressiontests/logging_tests/tests.py +++ b/tests/logging_tests/tests.py @@ -5,16 +5,16 @@ import logging import sys import warnings -from django.conf import compat_patch_logging_config, LazySettings +from django.conf import LazySettings from django.core import mail from django.test import TestCase, RequestFactory from django.test.utils import override_settings from django.utils.encoding import force_text -from django.utils.log import CallbackFilter, RequireDebugFalse +from django.utils.log import CallbackFilter, RequireDebugFalse, RequireDebugTrue from django.utils.six import StringIO from django.utils.unittest import skipUnless -from ..admin_scripts.tests import AdminScriptTestCase +from admin_scripts.tests import AdminScriptTestCase from .logconfig import MyEmailBackend @@ -40,46 +40,10 @@ OLD_LOGGING = { } -class PatchLoggingConfigTest(TestCase): - """ - Tests for backward-compat shim for #16288. These tests should be removed in - Django 1.6 when that shim and DeprecationWarning are removed. - - """ - def test_filter_added(self): - """ - Test that debug-false filter is added to mail_admins handler if it has - no filters. - - """ - config = copy.deepcopy(OLD_LOGGING) - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - compat_patch_logging_config(config) - self.assertEqual(len(w), 1) - - self.assertEqual( - config["handlers"]["mail_admins"]["filters"], - ['require_debug_false']) - - def test_filter_configuration(self): - """ - Test that the auto-added require_debug_false filter is an instance of - `RequireDebugFalse` filter class. - - """ - config = copy.deepcopy(OLD_LOGGING) - with warnings.catch_warnings(record=True): - compat_patch_logging_config(config) - - flt = config["filters"]["require_debug_false"] - self.assertEqual(flt["()"], "django.utils.log.RequireDebugFalse") - +class LoggingFiltersTest(TestCase): def test_require_debug_false_filter(self): """ Test the RequireDebugFalse filter class. - """ filter_ = RequireDebugFalse() @@ -89,32 +53,17 @@ class PatchLoggingConfigTest(TestCase): with self.settings(DEBUG=False): self.assertEqual(filter_.filter("record is not used"), True) - def test_no_patch_if_filters_key_exists(self): + def test_require_debug_true_filter(self): """ - Test that the logging configuration is not modified if the mail_admins - handler already has a "filters" key. - + Test the RequireDebugTrue filter class. """ - config = copy.deepcopy(OLD_LOGGING) - config["handlers"]["mail_admins"]["filters"] = [] - new_config = copy.deepcopy(config) - compat_patch_logging_config(new_config) + filter_ = RequireDebugTrue() - self.assertEqual(config, new_config) - - def test_no_patch_if_no_mail_admins_handler(self): - """ - Test that the logging configuration is not modified if the mail_admins - handler is not present. - - """ - config = copy.deepcopy(OLD_LOGGING) - config["handlers"].pop("mail_admins") - new_config = copy.deepcopy(config) - compat_patch_logging_config(new_config) - - self.assertEqual(config, new_config) + with self.settings(DEBUG=True): + self.assertEqual(filter_.filter("record is not used"), True) + with self.settings(DEBUG=False): + self.assertEqual(filter_.filter("record is not used"), False) class DefaultLoggingTest(TestCase): def setUp(self): @@ -144,24 +93,42 @@ class WarningLoggerTests(TestCase): and captured to the logging system """ def setUp(self): + # If tests are invoke with "-Wall" (or any -W flag actually) then + # warning logging gets disabled (see django/conf/__init__.py). However, + # these tests expect warnings to be logged, so manually force warnings + # to the logs. Use getattr() here because the logging capture state is + # undocumented and (I assume) brittle. + self._old_capture_state = bool(getattr(logging, '_warnings_showwarning', False)) + logging.captureWarnings(True) + + # this convoluted setup is to avoid printing this deprecation to + # stderr during test running - as the test runner forces deprecations + # to be displayed at the global py.warnings level self.logger = logging.getLogger('py.warnings') - self.old_stream = self.logger.handlers[0].stream + self.outputs = [] + self.old_streams = [] + for handler in self.logger.handlers: + self.old_streams.append(handler.stream) + self.outputs.append(StringIO()) + handler.stream = self.outputs[-1] def tearDown(self): - self.logger.handlers[0].stream = self.old_stream + for i, handler in enumerate(self.logger.handlers): + self.logger.handlers[i].stream = self.old_streams[i] + + # Reset warnings state. + logging.captureWarnings(self._old_capture_state) @override_settings(DEBUG=True) def test_warnings_capture(self): - output = StringIO() - self.logger.handlers[0].stream = output warnings.warn('Foo Deprecated', DeprecationWarning) - self.assertTrue('Foo Deprecated' in force_text(output.getvalue())) + output = force_text(self.outputs[0].getvalue()) + self.assertTrue('Foo Deprecated' in output) def test_warnings_capture_debug_false(self): - output = StringIO() - self.logger.handlers[0].stream = output warnings.warn('Foo Deprecated', DeprecationWarning) - self.assertFalse('Foo Deprecated' in force_text(output.getvalue())) + output = force_text(self.outputs[0].getvalue()) + self.assertFalse('Foo Deprecated' in output) class CallbackFilterTest(TestCase): @@ -189,7 +156,7 @@ class AdminEmailHandlerTest(TestCase): logger = logging.getLogger('django.request') def get_admin_email_handler(self, logger): - # Inspired from regressiontests/views/views.py: send_log() + # Inspired from views/views.py: send_log() # ensuring the AdminEmailHandler does not get filtered out # even with DEBUG=True. admin_email_handler = [ @@ -198,6 +165,10 @@ class AdminEmailHandlerTest(TestCase): ][0] return admin_email_handler + def test_fail_silently(self): + admin_email_handler = self.get_admin_email_handler(self.logger) + self.assertTrue(admin_email_handler.connection().fail_silently) + @override_settings( ADMINS=(('whatever admin', 'admin@example.com'),), EMAIL_SUBJECT_PREFIX='-SuperAwesomeSubject-' @@ -330,7 +301,7 @@ class AdminEmailHandlerTest(TestCase): orig_email_backend = admin_email_handler.email_backend mail.mail_admins = my_mail_admins admin_email_handler.email_backend = ( - 'regressiontests.logging_tests.logconfig.MyEmailBackend') + 'logging_tests.logconfig.MyEmailBackend') try: self.logger.error(message) @@ -381,5 +352,5 @@ class SettingsConfigureLogging(TestCase): def test_configure_initializes_logging(self): settings = LazySettings() settings.configure( - LOGGING_CONFIG='regressiontests.logging_tests.tests.dictConfig') + LOGGING_CONFIG='logging_tests.tests.dictConfig') self.assertTrue(dictConfig.called) diff --git a/tests/regressiontests/bulk_create/__init__.py b/tests/lookup/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/bulk_create/__init__.py +++ b/tests/lookup/__init__.py diff --git a/tests/modeltests/lookup/models.py b/tests/lookup/models.py index f388ddf403..f60c4962fd 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/lookup/models.py @@ -10,29 +10,16 @@ from django.db import models from django.utils import six from django.utils.encoding import python_2_unicode_compatible +from shared_models.models import Author, Book -class Author(models.Model): - name = models.CharField(max_length=100) - class Meta: - ordering = ('name', ) - -@python_2_unicode_compatible -class Article(models.Model): - headline = models.CharField(max_length=100) - pub_date = models.DateTimeField() - author = models.ForeignKey(Author, blank=True, null=True) - class Meta: - ordering = ('-pub_date', 'headline') - - def __str__(self): - return self.headline class Tag(models.Model): - articles = models.ManyToManyField(Article) + articles = models.ManyToManyField(Book) name = models.CharField(max_length=100) class Meta: ordering = ('name', ) + @python_2_unicode_compatible class Season(models.Model): year = models.PositiveSmallIntegerField() @@ -41,6 +28,7 @@ class Season(models.Model): def __str__(self): return six.text_type(self.year) + @python_2_unicode_compatible class Game(models.Model): season = models.ForeignKey(Season, related_name='games') @@ -50,6 +38,7 @@ class Game(models.Model): def __str__(self): return "%s at %s" % (self.away, self.home) + @python_2_unicode_compatible class Player(models.Model): name = models.CharField(max_length=100) diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py new file mode 100644 index 0000000000..6a6729ef22 --- /dev/null +++ b/tests/lookup/tests.py @@ -0,0 +1,687 @@ +from __future__ import absolute_import, unicode_literals + +from datetime import datetime +from operator import attrgetter + +from django.core.exceptions import FieldError +from django.test import TestCase, skipUnlessDBFeature +from django.utils import six + +from shared_models.models import Author, Book + +from .models import Tag, Game, Season, Player + + +class LookupTests(TestCase): + + def setUp(self): + # Create a few Authors. + self.au1 = Author(name='Author 1') + self.au1.save() + self.au2 = Author(name='Author 2') + self.au2.save() + # Create a couple of Books. + self.b1 = Book(title='Book 1', pubdate=datetime(2005, 7, 26), author=self.au1) + self.b1.save() + self.b2 = Book(title='Book 2', pubdate=datetime(2005, 7, 27), author=self.au1) + self.b2.save() + self.b3 = Book(title='Book 3', pubdate=datetime(2005, 7, 27), author=self.au1) + self.b3.save() + self.b4 = Book(title='Book 4', pubdate=datetime(2005, 7, 28), author=self.au1) + self.b4.save() + self.b5 = Book(title='Book 5', pubdate=datetime(2005, 8, 1, 9, 0), author=self.au2) + self.b5.save() + self.b6 = Book(title='Book 6', pubdate=datetime(2005, 8, 1, 8, 0), author=self.au2) + self.b6.save() + self.b7 = Book(title='Book 7', pubdate=datetime(2005, 7, 27), author=self.au2) + self.b7.save() + # Create a few Tags. + self.t1 = Tag(name='Tag 1') + self.t1.save() + self.t1.articles.add(self.b1, self.b2, self.b3) + self.t2 = Tag(name='Tag 2') + self.t2.save() + self.t2.articles.add(self.b3, self.b4, self.b5) + self.t3 = Tag(name='Tag 3') + self.t3.save() + self.t3.articles.add(self.b5, self.b6, self.b7) + + def test_exists(self): + # We can use .exists() to check that there are some + self.assertTrue(Book.objects.exists()) + for a in Book.objects.all(): + a.delete() + # There should be none now! + self.assertFalse(Book.objects.exists()) + + def test_lookup_int_as_str(self): + # Integer value can be queried using string + self.assertQuerysetEqual(Book.objects.filter(id__iexact=str(self.b1.id)), + ['<Book: Book 1>']) + + @skipUnlessDBFeature('supports_date_lookup_using_string') + def test_lookup_date_as_str(self): + # A date lookup can be performed using a string search + self.assertQuerysetEqual(Book.objects.filter(pubdate__startswith='2005'), + [ + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 7>', + '<Book: Book 1>', + ]) + + def test_iterator(self): + # Each QuerySet gets iterator(), which is a generator that "lazily" + # returns results using database-level iteration. + self.assertQuerysetEqual(Book.objects.iterator(), + [ + 'Book 5', + 'Book 6', + 'Book 4', + 'Book 2', + 'Book 3', + 'Book 7', + 'Book 1', + ], + transform=attrgetter('title')) + # iterator() can be used on any QuerySet. + self.assertQuerysetEqual( + Book.objects.filter(title__endswith='4').iterator(), + ['Book 4'], + transform=attrgetter('title')) + + def test_count(self): + # count() returns the number of objects matching search criteria. + self.assertEqual(Book.objects.count(), 7) + self.assertEqual(Book.objects.filter(pubdate__exact=datetime(2005, 7, 27)).count(), 3) + self.assertEqual(Book.objects.filter(title__startswith='Blah blah').count(), 0) + + # count() should respect sliced query sets. + articles = Book.objects.all() + self.assertEqual(articles.count(), 7) + self.assertEqual(articles[:4].count(), 4) + self.assertEqual(articles[1:100].count(), 6) + self.assertEqual(articles[10:100].count(), 0) + + # Date and date/time lookups can also be done with strings. + self.assertEqual(Book.objects.filter(pubdate__exact='2005-07-27').count(), 3) + + def test_in_bulk(self): + # in_bulk() takes a list of IDs and returns a dictionary mapping IDs to objects. + arts = Book.objects.in_bulk([self.b1.id, self.b2.id]) + self.assertEqual(arts[self.b1.id], self.b1) + self.assertEqual(arts[self.b2.id], self.b2) + self.assertEqual(Book.objects.in_bulk([self.b3.id]), {self.b3.id: self.b3}) + self.assertEqual(Book.objects.in_bulk(set([self.b3.id])), {self.b3.id: self.b3}) + self.assertEqual(Book.objects.in_bulk(frozenset([self.b3.id])), {self.b3.id: self.b3}) + self.assertEqual(Book.objects.in_bulk((self.b3.id,)), {self.b3.id: self.b3}) + self.assertEqual(Book.objects.in_bulk([1000]), {}) + self.assertEqual(Book.objects.in_bulk([]), {}) + self.assertEqual(Book.objects.in_bulk(iter([self.b1.id])), {self.b1.id: self.b1}) + self.assertEqual(Book.objects.in_bulk(iter([])), {}) + self.assertRaises(TypeError, Book.objects.in_bulk) + self.assertRaises(TypeError, Book.objects.in_bulk, name__startswith='Blah') + + def test_values(self): + # values() returns a list of dictionaries instead of object instances -- + # and you can specify which fields you want to retrieve. + identity = lambda x:x + self.assertQuerysetEqual(Book.objects.values('title'), + [ + {'title': 'Book 5'}, + {'title': 'Book 6'}, + {'title': 'Book 4'}, + {'title': 'Book 2'}, + {'title': 'Book 3'}, + {'title': 'Book 7'}, + {'title': 'Book 1'}, + ], + transform=identity) + self.assertQuerysetEqual( + Book.objects.filter(pubdate__exact=datetime(2005, 7, 27)).values('id'), + [{'id': self.b2.id}, {'id': self.b3.id}, {'id': self.b7.id}], + transform=identity) + self.assertQuerysetEqual(Book.objects.values('id', 'title'), + [ + {'id': self.b5.id, 'title': 'Book 5'}, + {'id': self.b6.id, 'title': 'Book 6'}, + {'id': self.b4.id, 'title': 'Book 4'}, + {'id': self.b2.id, 'title': 'Book 2'}, + {'id': self.b3.id, 'title': 'Book 3'}, + {'id': self.b7.id, 'title': 'Book 7'}, + {'id': self.b1.id, 'title': 'Book 1'}, + ], + transform=identity) + # You can use values() with iterator() for memory savings, + # because iterator() uses database-level iteration. + self.assertQuerysetEqual(Book.objects.values('id', 'title').iterator(), + [ + {'title': 'Book 5', 'id': self.b5.id}, + {'title': 'Book 6', 'id': self.b6.id}, + {'title': 'Book 4', 'id': self.b4.id}, + {'title': 'Book 2', 'id': self.b2.id}, + {'title': 'Book 3', 'id': self.b3.id}, + {'title': 'Book 7', 'id': self.b7.id}, + {'title': 'Book 1', 'id': self.b1.id}, + ], + transform=identity) + # The values() method works with "extra" fields specified in extra(select). + self.assertQuerysetEqual( + Book.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_one'), + [ + {'id': self.b5.id, 'id_plus_one': self.b5.id + 1}, + {'id': self.b6.id, 'id_plus_one': self.b6.id + 1}, + {'id': self.b4.id, 'id_plus_one': self.b4.id + 1}, + {'id': self.b2.id, 'id_plus_one': self.b2.id + 1}, + {'id': self.b3.id, 'id_plus_one': self.b3.id + 1}, + {'id': self.b7.id, 'id_plus_one': self.b7.id + 1}, + {'id': self.b1.id, 'id_plus_one': self.b1.id + 1}, + ], + transform=identity) + data = { + 'id_plus_one': 'id+1', + 'id_plus_two': 'id+2', + 'id_plus_three': 'id+3', + 'id_plus_four': 'id+4', + 'id_plus_five': 'id+5', + 'id_plus_six': 'id+6', + 'id_plus_seven': 'id+7', + 'id_plus_eight': 'id+8', + } + self.assertQuerysetEqual( + Book.objects.filter(id=self.b1.id).extra(select=data).values(*data.keys()), + [{ + 'id_plus_one': self.b1.id + 1, + 'id_plus_two': self.b1.id + 2, + 'id_plus_three': self.b1.id + 3, + 'id_plus_four': self.b1.id + 4, + 'id_plus_five': self.b1.id + 5, + 'id_plus_six': self.b1.id + 6, + 'id_plus_seven': self.b1.id + 7, + 'id_plus_eight': self.b1.id + 8, + }], transform=identity) + # You can specify fields from forward and reverse relations, just like filter(). + self.assertQuerysetEqual( + Book.objects.values('title', 'author__name'), + [ + {'title': self.b5.title, 'author__name': self.au2.name}, + {'title': self.b6.title, 'author__name': self.au2.name}, + {'title': self.b4.title, 'author__name': self.au1.name}, + {'title': self.b2.title, 'author__name': self.au1.name}, + {'title': self.b3.title, 'author__name': self.au1.name}, + {'title': self.b7.title, 'author__name': self.au2.name}, + {'title': self.b1.title, 'author__name': self.au1.name}, + ], transform=identity) + self.assertQuerysetEqual( + Author.objects.values('name', 'book__title').order_by('name', 'book__title'), + [ + {'name': self.au1.name, 'book__title': self.b1.title}, + {'name': self.au1.name, 'book__title': self.b2.title}, + {'name': self.au1.name, 'book__title': self.b3.title}, + {'name': self.au1.name, 'book__title': self.b4.title}, + {'name': self.au2.name, 'book__title': self.b5.title}, + {'name': self.au2.name, 'book__title': self.b6.title}, + {'name': self.au2.name, 'book__title': self.b7.title}, + ], transform=identity) + self.assertQuerysetEqual( + Author.objects.values('name', 'book__title', 'book__tag__name').order_by('name', 'book__title', 'book__tag__name'), + [ + {'name': self.au1.name, 'book__title': self.b1.title, 'book__tag__name': self.t1.name}, + {'name': self.au1.name, 'book__title': self.b2.title, 'book__tag__name': self.t1.name}, + {'name': self.au1.name, 'book__title': self.b3.title, 'book__tag__name': self.t1.name}, + {'name': self.au1.name, 'book__title': self.b3.title, 'book__tag__name': self.t2.name}, + {'name': self.au1.name, 'book__title': self.b4.title, 'book__tag__name': self.t2.name}, + {'name': self.au2.name, 'book__title': self.b5.title, 'book__tag__name': self.t2.name}, + {'name': self.au2.name, 'book__title': self.b5.title, 'book__tag__name': self.t3.name}, + {'name': self.au2.name, 'book__title': self.b6.title, 'book__tag__name': self.t3.name}, + {'name': self.au2.name, 'book__title': self.b7.title, 'book__tag__name': self.t3.name}, + ], transform=identity) + # However, an exception FieldDoesNotExist will be thrown if you specify + # a non-existent field name in values() (a field that is neither in the + # model nor in extra(select)). + self.assertRaises(FieldError, + Book.objects.extra(select={'id_plus_one': 'id + 1'}).values, + 'id', 'id_plus_two') + # If you don't specify field names to values(), all are returned. + self.assertQuerysetEqual(Book.objects.filter(id=self.b5.id).values(), + [{ + 'id': self.b5.id, + 'author_id': self.au2.id, + 'title': 'Book 5', + 'pages': 0, + 'pubdate': datetime(2005, 8, 1, 9, 0) + }], transform=identity) + + def test_values_list(self): + # values_list() is similar to values(), except that the results are + # returned as a list of tuples, rather than a list of dictionaries. + # Within each tuple, the order of the elements is the same as the order + # of fields in the values_list() call. + identity = lambda x:x + self.assertQuerysetEqual(Book.objects.values_list('title'), + [ + ('Book 5',), + ('Book 6',), + ('Book 4',), + ('Book 2',), + ('Book 3',), + ('Book 7',), + ('Book 1',), + ], transform=identity) + self.assertQuerysetEqual(Book.objects.values_list('id').order_by('id'), + [(self.b1.id,), (self.b2.id,), (self.b3.id,), (self.b4.id,), (self.b5.id,), (self.b6.id,), (self.b7.id,)], + transform=identity) + self.assertQuerysetEqual( + Book.objects.values_list('id', flat=True).order_by('id'), + [self.b1.id, self.b2.id, self.b3.id, self.b4.id, self.b5.id, self.b6.id, self.b7.id], + transform=identity) + self.assertQuerysetEqual( + Book.objects.extra(select={'id_plus_one': 'id+1'}) + .order_by('id').values_list('id'), + [(self.b1.id,), (self.b2.id,), (self.b3.id,), (self.b4.id,), (self.b5.id,), (self.b6.id,), (self.b7.id,)], + transform=identity) + self.assertQuerysetEqual( + Book.objects.extra(select={'id_plus_one': 'id+1'}) + .order_by('id').values_list('id_plus_one', 'id'), + [ + (self.b1.id+1, self.b1.id), + (self.b2.id+1, self.b2.id), + (self.b3.id+1, self.b3.id), + (self.b4.id+1, self.b4.id), + (self.b5.id+1, self.b5.id), + (self.b6.id+1, self.b6.id), + (self.b7.id+1, self.b7.id) + ], + transform=identity) + self.assertQuerysetEqual( + Book.objects.extra(select={'id_plus_one': 'id+1'}) + .order_by('id').values_list('id', 'id_plus_one'), + [ + (self.b1.id, self.b1.id+1), + (self.b2.id, self.b2.id+1), + (self.b3.id, self.b3.id+1), + (self.b4.id, self.b4.id+1), + (self.b5.id, self.b5.id+1), + (self.b6.id, self.b6.id+1), + (self.b7.id, self.b7.id+1) + ], + transform=identity) + self.assertQuerysetEqual( + Author.objects.values_list('name', 'book__title', 'book__tag__name').order_by('name', 'book__title', 'book__tag__name'), + [ + (self.au1.name, self.b1.title, self.t1.name), + (self.au1.name, self.b2.title, self.t1.name), + (self.au1.name, self.b3.title, self.t1.name), + (self.au1.name, self.b3.title, self.t2.name), + (self.au1.name, self.b4.title, self.t2.name), + (self.au2.name, self.b5.title, self.t2.name), + (self.au2.name, self.b5.title, self.t3.name), + (self.au2.name, self.b6.title, self.t3.name), + (self.au2.name, self.b7.title, self.t3.name), + ], transform=identity) + self.assertRaises(TypeError, Book.objects.values_list, 'id', 'title', flat=True) + + def test_get_next_previous_by(self): + # Every DateField and DateTimeField creates get_next_by_FOO() and + # get_previous_by_FOO() methods. In the case of identical date values, + # these methods will use the ID as a fallback check. This guarantees + # that no records are skipped or duplicated. + self.assertEqual(repr(self.b1.get_next_by_pubdate()), + '<Book: Book 2>') + self.assertEqual(repr(self.b2.get_next_by_pubdate()), + '<Book: Book 3>') + self.assertEqual(repr(self.b2.get_next_by_pubdate(title__endswith='6')), + '<Book: Book 6>') + self.assertEqual(repr(self.b3.get_next_by_pubdate()), + '<Book: Book 7>') + self.assertEqual(repr(self.b4.get_next_by_pubdate()), + '<Book: Book 6>') + self.assertRaises(Book.DoesNotExist, self.b5.get_next_by_pubdate) + self.assertEqual(repr(self.b6.get_next_by_pubdate()), + '<Book: Book 5>') + self.assertEqual(repr(self.b7.get_next_by_pubdate()), + '<Book: Book 4>') + + self.assertEqual(repr(self.b7.get_previous_by_pubdate()), + '<Book: Book 3>') + self.assertEqual(repr(self.b6.get_previous_by_pubdate()), + '<Book: Book 4>') + self.assertEqual(repr(self.b5.get_previous_by_pubdate()), + '<Book: Book 6>') + self.assertEqual(repr(self.b4.get_previous_by_pubdate()), + '<Book: Book 7>') + self.assertEqual(repr(self.b3.get_previous_by_pubdate()), + '<Book: Book 2>') + self.assertEqual(repr(self.b2.get_previous_by_pubdate()), + '<Book: Book 1>') + + def test_escaping(self): + # Underscores, percent signs and backslashes have special meaning in the + # underlying SQL code, but Django handles the quoting of them automatically. + b8 = Book(title='Book_ with underscore', pubdate=datetime(2005, 11, 20)) + b8.save() + self.assertQuerysetEqual(Book.objects.filter(title__startswith='Book'), + [ + '<Book: Book_ with underscore>', + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 7>', + '<Book: Book 1>', + ]) + self.assertQuerysetEqual(Book.objects.filter(title__startswith='Book_'), + ['<Book: Book_ with underscore>']) + b9 = Book(title='Book% with percent sign', pubdate=datetime(2005, 11, 21)) + b9.save() + self.assertQuerysetEqual(Book.objects.filter(title__startswith='Book'), + [ + '<Book: Book% with percent sign>', + '<Book: Book_ with underscore>', + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 7>', + '<Book: Book 1>', + ]) + self.assertQuerysetEqual(Book.objects.filter(title__startswith='Book%'), + ['<Book: Book% with percent sign>']) + b10 = Book(title='Book with \\ backslash', pubdate=datetime(2005, 11, 22)) + b10.save() + self.assertQuerysetEqual(Book.objects.filter(title__contains='\\'), + ['<Book: Book with \ backslash>']) + + def test_exclude(self): + b8 = Book.objects.create(title='Book_ with underscore', pubdate=datetime(2005, 11, 20)) + b9 = Book.objects.create(title='Book% with percent sign', pubdate=datetime(2005, 11, 21)) + b10 = Book.objects.create(title='Book with \\ backslash', pubdate=datetime(2005, 11, 22)) + + # exclude() is the opposite of filter() when doing lookups: + self.assertQuerysetEqual( + Book.objects.filter(title__contains='Book').exclude(title__contains='with'), + [ + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 7>', + '<Book: Book 1>', + ]) + self.assertQuerysetEqual(Book.objects.exclude(title__startswith="Book_"), + [ + '<Book: Book with \\ backslash>', + '<Book: Book% with percent sign>', + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 7>', + '<Book: Book 1>', + ]) + self.assertQuerysetEqual(Book.objects.exclude(title="Book 7"), + [ + '<Book: Book with \\ backslash>', + '<Book: Book% with percent sign>', + '<Book: Book_ with underscore>', + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 1>', + ]) + + def test_none(self): + # none() returns a QuerySet that behaves like any other QuerySet object + self.assertQuerysetEqual(Book.objects.none(), []) + self.assertQuerysetEqual( + Book.objects.none().filter(title__startswith='Book'), []) + self.assertQuerysetEqual( + Book.objects.filter(title__startswith='Book').none(), []) + self.assertEqual(Book.objects.none().count(), 0) + self.assertEqual( + Book.objects.none().update(title="This should not take effect"), 0) + self.assertQuerysetEqual( + [article for article in Book.objects.none().iterator()], + []) + + def test_in(self): + # using __in with an empty list should return an empty query set + self.assertQuerysetEqual(Book.objects.filter(id__in=[]), []) + self.assertQuerysetEqual(Book.objects.exclude(id__in=[]), + [ + '<Book: Book 5>', + '<Book: Book 6>', + '<Book: Book 4>', + '<Book: Book 2>', + '<Book: Book 3>', + '<Book: Book 7>', + '<Book: Book 1>', + ]) + + def test_error_messages(self): + # Programming errors are pointed out with nice error messages + with six.assertRaisesRegex(self, FieldError, "Cannot resolve keyword 'pubdate_year' " + "into field. Choices are: .+"): + Book.objects.filter(pubdate_year='2005').count() + + with self.assertRaises(FieldError, msg="Join on field 'title' not permitted. " + "Did you misspell 'starts' for the lookup type?"): + Book.objects.filter(title__starts='Book') + + def test_regex(self): + # Create some articles with a bit more interesting names for testing field lookups: + for a in Book.objects.all(): + a.delete() + now = datetime.now() + b1 = Book(pubdate=now, title='f') + b1.save() + b2 = Book(pubdate=now, title='fo') + b2.save() + b3 = Book(pubdate=now, title='foo') + b3.save() + b4 = Book(pubdate=now, title='fooo') + b4.save() + b5 = Book(pubdate=now, title='hey-Foo') + b5.save() + b6 = Book(pubdate=now, title='bar') + b6.save() + b7 = Book(pubdate=now, title='AbBa') + b7.save() + b8 = Book(pubdate=now, title='baz') + b8.save() + b9 = Book(pubdate=now, title='baxZ') + b9.save() + # zero-or-more + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'fo*'), + ['<Book: f>', '<Book: fo>', '<Book: foo>', '<Book: fooo>']) + self.assertQuerysetEqual(Book.objects.filter(title__iregex=r'fo*'), + [ + '<Book: f>', + '<Book: fo>', + '<Book: foo>', + '<Book: fooo>', + '<Book: hey-Foo>', + ]) + # one-or-more + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'fo+'), + ['<Book: fo>', '<Book: foo>', '<Book: fooo>']) + # wildcard + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'fooo?'), + ['<Book: foo>', '<Book: fooo>']) + # leading anchor + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'^b'), + ['<Book: bar>', '<Book: baxZ>', '<Book: baz>']) + self.assertQuerysetEqual(Book.objects.filter(title__iregex=r'^a'), + ['<Book: AbBa>']) + # trailing anchor + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'z$'), + ['<Book: baz>']) + self.assertQuerysetEqual(Book.objects.filter(title__iregex=r'z$'), + ['<Book: baxZ>', '<Book: baz>']) + # character sets + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'ba[rz]'), + ['<Book: bar>', '<Book: baz>']) + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'ba.[RxZ]'), + ['<Book: baxZ>']) + self.assertQuerysetEqual(Book.objects.filter(title__iregex=r'ba[RxZ]'), + ['<Book: bar>', '<Book: baxZ>', '<Book: baz>']) + + # and more articles: + b10 = Book(pubdate=now, title='foobar') + b10.save() + b11 = Book(pubdate=now, title='foobaz') + b11.save() + b12 = Book(pubdate=now, title='ooF') + b12.save() + b13 = Book(pubdate=now, title='foobarbaz') + b13.save() + b14 = Book(pubdate=now, title='zoocarfaz') + b14.save() + b15 = Book(pubdate=now, title='barfoobaz') + b15.save() + b16 = Book(pubdate=now, title='bazbaRFOO') + b16.save() + + # alternation + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'oo(f|b)'), + [ + '<Book: barfoobaz>', + '<Book: foobar>', + '<Book: foobarbaz>', + '<Book: foobaz>', + ]) + self.assertQuerysetEqual(Book.objects.filter(title__iregex=r'oo(f|b)'), + [ + '<Book: barfoobaz>', + '<Book: foobar>', + '<Book: foobarbaz>', + '<Book: foobaz>', + '<Book: ooF>', + ]) + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'^foo(f|b)'), + ['<Book: foobar>', '<Book: foobarbaz>', '<Book: foobaz>']) + + # greedy matching + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'b.*az'), + [ + '<Book: barfoobaz>', + '<Book: baz>', + '<Book: bazbaRFOO>', + '<Book: foobarbaz>', + '<Book: foobaz>', + ]) + self.assertQuerysetEqual(Book.objects.filter(title__iregex=r'b.*ar'), + [ + '<Book: bar>', + '<Book: barfoobaz>', + '<Book: bazbaRFOO>', + '<Book: foobar>', + '<Book: foobarbaz>', + ]) + + @skipUnlessDBFeature('supports_regex_backreferencing') + def test_regex_backreferencing(self): + # grouping and backreferences + now = datetime.now() + b10 = Book(pubdate=now, title='foobar') + b10.save() + b11 = Book(pubdate=now, title='foobaz') + b11.save() + b12 = Book(pubdate=now, title='ooF') + b12.save() + b13 = Book(pubdate=now, title='foobarbaz') + b13.save() + b14 = Book(pubdate=now, title='zoocarfaz') + b14.save() + b15 = Book(pubdate=now, title='barfoobaz') + b15.save() + b16 = Book(pubdate=now, title='bazbaRFOO') + b16.save() + self.assertQuerysetEqual(Book.objects.filter(title__regex=r'b(.).*b\1'), + ['<Book: barfoobaz>', '<Book: bazbaRFOO>', '<Book: foobarbaz>']) + + def test_nonfield_lookups(self): + """ + Ensure that a lookup query containing non-fields raises the proper + exception. + """ + with self.assertRaises(FieldError): + Book.objects.filter(name__blahblah=99) + with self.assertRaises(FieldError): + Book.objects.filter(name__blahblah__exact=99) + with self.assertRaises(FieldError): + Book.objects.filter(blahblah=99) + + def test_lookup_collision(self): + """ + Ensure that genuine field names don't collide with built-in lookup + types ('year', 'gt', 'range', 'in' etc.). + Refs #11670. + """ + + # Here we're using 'gt' as a code number for the year, e.g. 111=>2009. + season_2009 = Season.objects.create(year=2009, gt=111) + season_2009.games.create(home="Houston Astros", away="St. Louis Cardinals") + season_2010 = Season.objects.create(year=2010, gt=222) + season_2010.games.create(home="Houston Astros", away="Chicago Cubs") + season_2010.games.create(home="Houston Astros", away="Milwaukee Brewers") + season_2010.games.create(home="Houston Astros", away="St. Louis Cardinals") + season_2011 = Season.objects.create(year=2011, gt=333) + season_2011.games.create(home="Houston Astros", away="St. Louis Cardinals") + season_2011.games.create(home="Houston Astros", away="Milwaukee Brewers") + hunter_pence = Player.objects.create(name="Hunter Pence") + hunter_pence.games = Game.objects.filter(season__year__in=[2009, 2010]) + pudge = Player.objects.create(name="Ivan Rodriquez") + pudge.games = Game.objects.filter(season__year=2009) + pedro_feliz = Player.objects.create(name="Pedro Feliz") + pedro_feliz.games = Game.objects.filter(season__year__in=[2011]) + johnson = Player.objects.create(name="Johnson") + johnson.games = Game.objects.filter(season__year__in=[2011]) + + # Games in 2010 + self.assertEqual(Game.objects.filter(season__year=2010).count(), 3) + self.assertEqual(Game.objects.filter(season__year__exact=2010).count(), 3) + self.assertEqual(Game.objects.filter(season__gt=222).count(), 3) + self.assertEqual(Game.objects.filter(season__gt__exact=222).count(), 3) + + # Games in 2011 + self.assertEqual(Game.objects.filter(season__year=2011).count(), 2) + self.assertEqual(Game.objects.filter(season__year__exact=2011).count(), 2) + self.assertEqual(Game.objects.filter(season__gt=333).count(), 2) + self.assertEqual(Game.objects.filter(season__gt__exact=333).count(), 2) + self.assertEqual(Game.objects.filter(season__year__gt=2010).count(), 2) + self.assertEqual(Game.objects.filter(season__gt__gt=222).count(), 2) + + # Games played in 2010 and 2011 + self.assertEqual(Game.objects.filter(season__year__in=[2010, 2011]).count(), 5) + self.assertEqual(Game.objects.filter(season__year__gt=2009).count(), 5) + self.assertEqual(Game.objects.filter(season__gt__in=[222, 333]).count(), 5) + self.assertEqual(Game.objects.filter(season__gt__gt=111).count(), 5) + + # Players who played in 2009 + self.assertEqual(Player.objects.filter(games__season__year=2009).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__year__exact=2009).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__gt=111).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__gt__exact=111).distinct().count(), 2) + + # Players who played in 2010 + self.assertEqual(Player.objects.filter(games__season__year=2010).distinct().count(), 1) + self.assertEqual(Player.objects.filter(games__season__year__exact=2010).distinct().count(), 1) + self.assertEqual(Player.objects.filter(games__season__gt=222).distinct().count(), 1) + self.assertEqual(Player.objects.filter(games__season__gt__exact=222).distinct().count(), 1) + + # Players who played in 2011 + self.assertEqual(Player.objects.filter(games__season__year=2011).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__year__exact=2011).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__gt=333).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__year__gt=2010).distinct().count(), 2) + self.assertEqual(Player.objects.filter(games__season__gt__gt=222).distinct().count(), 2) diff --git a/tests/regressiontests/cache/__init__.py b/tests/m2m_and_m2o/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/cache/__init__.py +++ b/tests/m2m_and_m2o/__init__.py diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/m2m_and_m2o/models.py index 99c7b01017..99c7b01017 100644 --- a/tests/modeltests/m2m_and_m2o/models.py +++ b/tests/m2m_and_m2o/models.py diff --git a/tests/modeltests/m2m_and_m2o/tests.py b/tests/m2m_and_m2o/tests.py index 77f2eb3b09..77f2eb3b09 100644 --- a/tests/modeltests/m2m_and_m2o/tests.py +++ b/tests/m2m_and_m2o/tests.py diff --git a/tests/regressiontests/comment_tests/__init__.py b/tests/m2m_intermediary/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/comment_tests/__init__.py +++ b/tests/m2m_intermediary/__init__.py diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/m2m_intermediary/models.py index e9ae422afb..e9ae422afb 100644 --- a/tests/modeltests/m2m_intermediary/models.py +++ b/tests/m2m_intermediary/models.py diff --git a/tests/modeltests/m2m_intermediary/tests.py b/tests/m2m_intermediary/tests.py index f261f23546..f261f23546 100644 --- a/tests/modeltests/m2m_intermediary/tests.py +++ b/tests/m2m_intermediary/tests.py diff --git a/tests/regressiontests/context_processors/__init__.py b/tests/m2m_multiple/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/context_processors/__init__.py +++ b/tests/m2m_multiple/__init__.py diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/m2m_multiple/models.py index c2e3b030a4..c2e3b030a4 100644 --- a/tests/modeltests/m2m_multiple/models.py +++ b/tests/m2m_multiple/models.py diff --git a/tests/modeltests/m2m_multiple/tests.py b/tests/m2m_multiple/tests.py index 7bf88f99bb..7bf88f99bb 100644 --- a/tests/modeltests/m2m_multiple/tests.py +++ b/tests/m2m_multiple/tests.py diff --git a/tests/regressiontests/createsuperuser/__init__.py b/tests/m2m_recursive/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/createsuperuser/__init__.py +++ b/tests/m2m_recursive/__init__.py diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/m2m_recursive/models.py index b69930208c..b69930208c 100644 --- a/tests/modeltests/m2m_recursive/models.py +++ b/tests/m2m_recursive/models.py diff --git a/tests/modeltests/m2m_recursive/tests.py b/tests/m2m_recursive/tests.py index a3f2c670d6..a3f2c670d6 100644 --- a/tests/modeltests/m2m_recursive/tests.py +++ b/tests/m2m_recursive/tests.py diff --git a/tests/regressiontests/csrf_tests/__init__.py b/tests/m2m_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/csrf_tests/__init__.py +++ b/tests/m2m_regress/__init__.py diff --git a/tests/regressiontests/m2m_regress/models.py b/tests/m2m_regress/models.py index 1a4a6df354..1a4a6df354 100644 --- a/tests/regressiontests/m2m_regress/models.py +++ b/tests/m2m_regress/models.py diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/m2m_regress/tests.py index 3dc1d2417c..884fc097a1 100644 --- a/tests/regressiontests/m2m_regress/tests.py +++ b/tests/m2m_regress/tests.py @@ -5,7 +5,7 @@ from django.test import TestCase from django.utils import six from .models import (SelfRefer, Tag, TagCollection, Entry, SelfReferChild, - SelfReferChildSibling, Worksheet, RegressionModelSplit) + SelfReferChildSibling, Worksheet, RegressionModelSplit, Line) class M2MRegressionTests(TestCase): @@ -96,3 +96,16 @@ class M2MRegressionTests(TestCase): # causes a TypeError in add_lazy_relation m1 = RegressionModelSplit(name='1') m1.save() + + def test_m2m_filter(self): + worksheet = Worksheet.objects.create(id=1) + line_hi = Line.objects.create(name="hi") + line_bye = Line.objects.create(name="bye") + + worksheet.lines = [line_hi, line_bye] + hi = worksheet.lines.filter(name="hi") + + worksheet.lines = hi + self.assertEqual(1, worksheet.lines.count()) + self.assertEqual(1, hi.count()) + diff --git a/tests/regressiontests/file_storage/__init__.py b/tests/m2m_signals/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/file_storage/__init__.py +++ b/tests/m2m_signals/__init__.py diff --git a/tests/modeltests/m2m_signals/models.py b/tests/m2m_signals/models.py index e997d87f21..e997d87f21 100644 --- a/tests/modeltests/m2m_signals/models.py +++ b/tests/m2m_signals/models.py diff --git a/tests/modeltests/m2m_signals/tests.py b/tests/m2m_signals/tests.py index d3d2a74c70..d3d2a74c70 100644 --- a/tests/modeltests/m2m_signals/tests.py +++ b/tests/m2m_signals/tests.py diff --git a/tests/modeltests/m2m_through/__init__.py b/tests/m2m_through/__init__.py index 139597f9cb..139597f9cb 100644 --- a/tests/modeltests/m2m_through/__init__.py +++ b/tests/m2m_through/__init__.py diff --git a/tests/modeltests/m2m_through/models.py b/tests/m2m_through/models.py index a896f6d9ff..a896f6d9ff 100644 --- a/tests/modeltests/m2m_through/models.py +++ b/tests/m2m_through/models.py diff --git a/tests/modeltests/m2m_through/tests.py b/tests/m2m_through/tests.py index 259dc68a0b..259dc68a0b 100644 --- a/tests/modeltests/m2m_through/tests.py +++ b/tests/m2m_through/tests.py diff --git a/tests/modeltests/unmanaged_models/__init__.py b/tests/m2m_through_regress/__init__.py index 139597f9cb..139597f9cb 100644 --- a/tests/modeltests/unmanaged_models/__init__.py +++ b/tests/m2m_through_regress/__init__.py diff --git a/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json b/tests/m2m_through_regress/fixtures/m2m_through.json index 6f24886f02..6f24886f02 100644 --- a/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json +++ b/tests/m2m_through_regress/fixtures/m2m_through.json diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/m2m_through_regress/models.py index 91e1aa8cc1..91e1aa8cc1 100644 --- a/tests/regressiontests/m2m_through_regress/models.py +++ b/tests/m2m_through_regress/models.py diff --git a/tests/regressiontests/m2m_through_regress/tests.py b/tests/m2m_through_regress/tests.py index 17a4525442..5ac10462fa 100644 --- a/tests/regressiontests/m2m_through_regress/tests.py +++ b/tests/m2m_through_regress/tests.py @@ -73,12 +73,12 @@ class M2MThroughTestCase(TestCase): 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) + self.assertJSONEqual(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(), """ + self.assertXMLEqual(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"> @@ -232,4 +232,4 @@ class ThroughLoadDataTestCase(TestCase): "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"}}]""") + self.assertJSONEqual(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/tests/regressiontests/custom_columns_regress/__init__.py b/tests/m2o_recursive/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/custom_columns_regress/__init__.py +++ b/tests/m2o_recursive/__init__.py diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/m2o_recursive/models.py index 2775d713dd..2775d713dd 100644 --- a/tests/modeltests/m2o_recursive/models.py +++ b/tests/m2o_recursive/models.py diff --git a/tests/modeltests/m2o_recursive/tests.py b/tests/m2o_recursive/tests.py index fa04c74cca..fa04c74cca 100644 --- a/tests/modeltests/m2o_recursive/tests.py +++ b/tests/m2o_recursive/tests.py diff --git a/tests/regressiontests/m2m_through_regress/__init__.py b/tests/mail/__init__.py index 139597f9cb..139597f9cb 100644 --- a/tests/regressiontests/m2m_through_regress/__init__.py +++ b/tests/mail/__init__.py diff --git a/tests/regressiontests/mail/custombackend.py b/tests/mail/custombackend.py index 6b0e15ad0a..6b0e15ad0a 100644 --- a/tests/regressiontests/mail/custombackend.py +++ b/tests/mail/custombackend.py diff --git a/tests/regressiontests/mail/models.py b/tests/mail/models.py index 7ff128fa69..7ff128fa69 100644 --- a/tests/regressiontests/mail/models.py +++ b/tests/mail/models.py diff --git a/tests/regressiontests/mail/tests.py b/tests/mail/tests.py index b798cb21aa..0a843db9e7 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/mail/tests.py @@ -90,7 +90,17 @@ class MailTests(TestCase): """ 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: 7bit\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent') + + self.assertEqual(sorted(email.message().items()), [ + ('Content-Transfer-Encoding', '7bit'), + ('Content-Type', 'text/plain; charset="utf-8"'), + ('From', 'from@example.com'), + ('MIME-Version', '1.0'), + ('Message-ID', 'foo'), + ('Subject', 'subject'), + ('To', 'to@example.com'), + ('date', 'Fri, 09 Nov 2001 01:08:47 -0000'), + ]) def test_from_header(self): """ @@ -233,7 +243,7 @@ class MailTests(TestCase): def test_custom_backend(self): """Test custom backend defined in this suite.""" - conn = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') + conn = mail.get_connection('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]) @@ -261,13 +271,13 @@ class MailTests(TestCase): mail.outbox = [] # Send using non-default connection - connection = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') + connection = mail.get_connection('mail.custombackend.EmailBackend') send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) self.assertEqual(mail.outbox, []) self.assertEqual(len(connection.test_outbox), 1) self.assertEqual(connection.test_outbox[0].subject, 'Subject') - connection = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') + connection = mail.get_connection('mail.custombackend.EmailBackend') send_mass_mail([ ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']), ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com']), @@ -277,13 +287,13 @@ class MailTests(TestCase): self.assertEqual(connection.test_outbox[0].subject, 'Subject1') self.assertEqual(connection.test_outbox[1].subject, 'Subject2') - connection = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') + connection = mail.get_connection('mail.custombackend.EmailBackend') mail_admins('Admin message', 'Content', connection=connection) self.assertEqual(mail.outbox, []) self.assertEqual(len(connection.test_outbox), 1) self.assertEqual(connection.test_outbox[0].subject, '[Django] Admin message') - connection = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') + connection = mail.get_connection('mail.custombackend.EmailBackend') mail_managers('Manager message', 'Content', connection=connection) self.assertEqual(mail.outbox, []) self.assertEqual(len(connection.test_outbox), 1) @@ -482,6 +492,16 @@ class BaseEmailBackendTests(object): self.assertEqual(message.get('from'), "tester") self.assertEqual(message.get('to'), "django") + def test_close_connection(self): + """ + Test that connection can be closed (even when not explicitely opened) + """ + conn = mail.get_connection(username='', password='') + try: + conn.close() + except Exception as e: + self.fail("close() unexpectedly raised an exception: %s" % e) + class LocmemBackendTests(BaseEmailBackendTests, TestCase): email_backend = 'django.core.mail.backends.locmem.EmailBackend' @@ -649,9 +669,9 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): asyncore.close_all() def stop(self): - assert self.active - self.active = False - self.join() + if self.active: + self.active = False + self.join() class SMTPBackendTests(BaseEmailBackendTests, TestCase): @@ -705,3 +725,16 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase): backend = smtp.EmailBackend(username='', password='') self.assertEqual(backend.username, '') self.assertEqual(backend.password, '') + + def test_server_stopped(self): + """ + Test that closing the backend while the SMTP server is stopped doesn't + raise an exception. + """ + backend = smtp.EmailBackend(username='', password='') + backend.open() + self.server.stop() + try: + backend.close() + except Exception as e: + self.fail("close() unexpectedly raised an exception: %s" % e) diff --git a/tests/regressiontests/custom_managers_regress/__init__.py b/tests/managers_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/custom_managers_regress/__init__.py +++ b/tests/managers_regress/__init__.py diff --git a/tests/regressiontests/managers_regress/models.py b/tests/managers_regress/models.py index d72970d86e..d8dd22ec9a 100644 --- a/tests/regressiontests/managers_regress/models.py +++ b/tests/managers_regress/models.py @@ -7,18 +7,18 @@ from django.utils.encoding import python_2_unicode_compatible class OnlyFred(models.Manager): - def get_query_set(self): - return super(OnlyFred, self).get_query_set().filter(name='fred') + def get_queryset(self): + return super(OnlyFred, self).get_queryset().filter(name='fred') class OnlyBarney(models.Manager): - def get_query_set(self): - return super(OnlyBarney, self).get_query_set().filter(name='barney') + def get_queryset(self): + return super(OnlyBarney, self).get_queryset().filter(name='barney') class Value42(models.Manager): - def get_query_set(self): - return super(Value42, self).get_query_set().filter(value=42) + def get_queryset(self): + return super(Value42, self).get_queryset().filter(value=42) class AbstractBase1(models.Model): diff --git a/tests/regressiontests/managers_regress/tests.py b/tests/managers_regress/tests.py index 45059be4e5..45059be4e5 100644 --- a/tests/regressiontests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py diff --git a/tests/regressiontests/datatypes/__init__.py b/tests/many_to_many/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/datatypes/__init__.py +++ b/tests/many_to_many/__init__.py diff --git a/tests/modeltests/many_to_many/models.py b/tests/many_to_many/models.py index a196c85092..31793b3974 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/many_to_many/models.py @@ -6,6 +6,7 @@ To define a many-to-many relationship, use ``ManyToManyField()``. In this example, an ``Article`` can be published in multiple ``Publication`` objects, and a ``Publication`` has multiple ``Article`` objects. """ +from __future__ import unicode_literals from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -24,7 +25,9 @@ class Publication(models.Model): @python_2_unicode_compatible class Article(models.Model): headline = models.CharField(max_length=100) - publications = models.ManyToManyField(Publication) + # Assign a unicode string as name to make sure the intermediary model is + # correctly created. Refs #20207 + publications = models.ManyToManyField(Publication, name='publications') def __str__(self): return self.headline diff --git a/tests/modeltests/many_to_many/tests.py b/tests/many_to_many/tests.py index 7d30379b94..7d30379b94 100644 --- a/tests/modeltests/many_to_many/tests.py +++ b/tests/many_to_many/tests.py diff --git a/tests/regressiontests/dates/__init__.py b/tests/many_to_one/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/dates/__init__.py +++ b/tests/many_to_one/__init__.py diff --git a/tests/modeltests/many_to_one/models.py b/tests/many_to_one/models.py index 4e2ed67eea..4e2ed67eea 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/many_to_one/models.py diff --git a/tests/modeltests/many_to_one/tests.py b/tests/many_to_one/tests.py index 44ae689dd4..a4f87a3283 100644 --- a/tests/modeltests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -1,7 +1,7 @@ from __future__ import absolute_import from copy import deepcopy -from datetime import datetime +import datetime from django.core.exceptions import MultipleObjectsReturned, FieldError from django.test import TestCase @@ -20,7 +20,7 @@ class ManyToOneTests(TestCase): self.r2.save() # Create an Article. self.a = Article(id=None, headline="This is a test", - pub_date=datetime(2005, 7, 27), reporter=self.r) + pub_date=datetime.date(2005, 7, 27), reporter=self.r) self.a.save() def test_get(self): @@ -36,25 +36,25 @@ class ManyToOneTests(TestCase): # You can also instantiate an Article by passing the Reporter's ID # instead of a Reporter object. a3 = Article(id=None, headline="Third article", - pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + pub_date=datetime.date(2005, 7, 27), reporter_id=self.r.id) a3.save() self.assertEqual(a3.reporter.id, self.r.id) # Similarly, the reporter ID can be a string. a4 = Article(id=None, headline="Fourth article", - pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + pub_date=datetime.date(2005, 7, 27), reporter_id=str(self.r.id)) a4.save() self.assertEqual(repr(a4.reporter), "<Reporter: John Smith>") def test_add(self): # Create an Article via the Reporter object. new_article = self.r.article_set.create(headline="John's second story", - pub_date=datetime(2005, 7, 29)) + pub_date=datetime.date(2005, 7, 29)) self.assertEqual(repr(new_article), "<Article: John's second story>") self.assertEqual(new_article.reporter.id, self.r.id) # Create a new article, and add it to the article set. - new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) + new_article2 = Article(headline="Paul's story", pub_date=datetime.date(2006, 1, 17)) self.r.article_set.add(new_article2) self.assertEqual(new_article2.reporter.id, self.r.id) self.assertQuerysetEqual(self.r.article_set.all(), @@ -80,9 +80,9 @@ class ManyToOneTests(TestCase): def test_assign(self): new_article = self.r.article_set.create(headline="John's second story", - pub_date=datetime(2005, 7, 29)) + pub_date=datetime.date(2005, 7, 29)) new_article2 = self.r2.article_set.create(headline="Paul's story", - pub_date=datetime(2006, 1, 17)) + pub_date=datetime.date(2006, 1, 17)) # Assign the article to the reporter directly using the descriptor. new_article2.reporter = self.r new_article2.save() @@ -118,9 +118,9 @@ class ManyToOneTests(TestCase): def test_selects(self): new_article = self.r.article_set.create(headline="John's second story", - pub_date=datetime(2005, 7, 29)) + pub_date=datetime.date(2005, 7, 29)) new_article2 = self.r2.article_set.create(headline="Paul's story", - pub_date=datetime(2006, 1, 17)) + pub_date=datetime.date(2006, 1, 17)) # Reporter objects have access to their related Article objects. self.assertQuerysetEqual(self.r.article_set.all(), [ "<Article: John's second story>", @@ -237,9 +237,9 @@ class ManyToOneTests(TestCase): def test_reverse_selects(self): a3 = Article.objects.create(id=None, headline="Third article", - pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + pub_date=datetime.date(2005, 7, 27), reporter_id=self.r.id) a4 = Article.objects.create(id=None, headline="Fourth article", - pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + pub_date=datetime.date(2005, 7, 27), reporter_id=str(self.r.id)) # Reporters can be queried self.assertQuerysetEqual(Reporter.objects.filter(id__exact=self.r.id), ["<Reporter: John Smith>"]) @@ -316,33 +316,33 @@ class ManyToOneTests(TestCase): # objects (Reporters). r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com') r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com') - a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1) - a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2) + Article.objects.create(headline='First', pub_date=datetime.date(1980, 4, 23), reporter=r1) + Article.objects.create(headline='Second', pub_date=datetime.date(1980, 4, 23), reporter=r2) self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'day')), [ - datetime(1980, 4, 23, 0, 0), - datetime(2005, 7, 27, 0, 0), + datetime.date(1980, 4, 23), + datetime.date(2005, 7, 27), ]) self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'month')), [ - datetime(1980, 4, 1, 0, 0), - datetime(2005, 7, 1, 0, 0), + datetime.date(1980, 4, 1), + datetime.date(2005, 7, 1), ]) self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'year')), [ - datetime(1980, 1, 1, 0, 0), - datetime(2005, 1, 1, 0, 0), + datetime.date(1980, 1, 1), + datetime.date(2005, 1, 1), ]) def test_delete(self): new_article = self.r.article_set.create(headline="John's second story", - pub_date=datetime(2005, 7, 29)) + pub_date=datetime.date(2005, 7, 29)) new_article2 = self.r2.article_set.create(headline="Paul's story", - pub_date=datetime(2006, 1, 17)) + pub_date=datetime.date(2006, 1, 17)) a3 = Article.objects.create(id=None, headline="Third article", - pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + pub_date=datetime.date(2005, 7, 27), reporter_id=self.r.id) a4 = Article.objects.create(id=None, headline="Fourth article", - pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + pub_date=datetime.date(2005, 7, 27), reporter_id=str(self.r.id)) # If you delete a reporter, his articles will be deleted. self.assertQuerysetEqual(Article.objects.all(), [ @@ -383,7 +383,7 @@ class ManyToOneTests(TestCase): # for a ForeignKey. a2, created = Article.objects.get_or_create(id=None, headline="John's second test", - pub_date=datetime(2011, 5, 7), + pub_date=datetime.date(2011, 5, 7), reporter_id=self.r.id) self.assertTrue(created) self.assertEqual(a2.reporter.id, self.r.id) @@ -398,7 +398,7 @@ class ManyToOneTests(TestCase): # Create an Article by Paul for the same date. a3 = Article.objects.create(id=None, headline="Paul's commentary", - pub_date=datetime(2011, 5, 7), + pub_date=datetime.date(2011, 5, 7), reporter_id=self.r2.id) self.assertEqual(a3.reporter.id, self.r2.id) @@ -407,7 +407,7 @@ class ManyToOneTests(TestCase): Article.objects.get, reporter_id=self.r.id) self.assertEqual(repr(a3), repr(Article.objects.get(reporter_id=self.r2.id, - pub_date=datetime(2011, 5, 7)))) + pub_date=datetime.date(2011, 5, 7)))) def test_manager_class_caching(self): r1 = Reporter.objects.create(first_name='Mike') @@ -425,7 +425,7 @@ class ManyToOneTests(TestCase): email='john.smith@example.com') lazy = ugettext_lazy('test') reporter.article_set.create(headline=lazy, - pub_date=datetime(2011, 6, 10)) + pub_date=datetime.date(2011, 6, 10)) notlazy = six.text_type(lazy) article = reporter.article_set.get() self.assertEqual(article.headline, notlazy) diff --git a/tests/regressiontests/db_typecasts/__init__.py b/tests/many_to_one_null/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/db_typecasts/__init__.py +++ b/tests/many_to_one_null/__init__.py diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/many_to_one_null/models.py index e00ca85928..e00ca85928 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/many_to_one_null/models.py diff --git a/tests/modeltests/many_to_one_null/tests.py b/tests/many_to_one_null/tests.py index 4de44b5e64..4de44b5e64 100644 --- a/tests/modeltests/many_to_one_null/tests.py +++ b/tests/many_to_one_null/tests.py diff --git a/tests/regressiontests/decorators/__init__.py b/tests/many_to_one_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/decorators/__init__.py +++ b/tests/many_to_one_regress/__init__.py diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/many_to_one_regress/models.py index f3727820f8..0ac0871793 100644 --- a/tests/regressiontests/many_to_one_regress/models.py +++ b/tests/many_to_one_regress/models.py @@ -48,3 +48,9 @@ class Relation(models.Model): def __str__(self): return "%s - %s" % (self.left.category.name, self.right.category.name) + +class Car(models.Model): + make = models.CharField(max_length=100, null=True, unique=True) + +class Driver(models.Model): + car = models.ForeignKey(Car, to_field='make', null=True, related_name='drivers') diff --git a/tests/regressiontests/many_to_one_regress/tests.py b/tests/many_to_one_regress/tests.py index d980d7437c..a9969a7e90 100644 --- a/tests/regressiontests/many_to_one_regress/tests.py +++ b/tests/many_to_one_regress/tests.py @@ -4,7 +4,8 @@ from django.db import models from django.test import TestCase from django.utils import six -from .models import First, Third, Parent, Child, Category, Record, Relation +from .models import ( + First, Third, Parent, Child, Category, Record, Relation, Car, Driver) class ManyToOneRegressionTests(TestCase): @@ -111,3 +112,25 @@ class ManyToOneRegressionTests(TestCase): # of a model, and interrogate its related field. cat = models.ForeignKey(Category) self.assertEqual('id', cat.rel.get_related_field().name) + + def test_relation_unsaved(self): + # Test that the <field>_set manager does not join on Null value fields (#17541) + Third.objects.create(name='Third 1') + Third.objects.create(name='Third 2') + th = Third(name="testing") + # The object isn't saved an thus the relation field is null - we won't even + # execute a query in this case. + with self.assertNumQueries(0): + self.assertEqual(th.child_set.count(), 0) + th.save() + # Now the model is saved, so we will need to execute an query. + with self.assertNumQueries(1): + self.assertEqual(th.child_set.count(), 0) + + def test_related_null_to_field(self): + c1 = Car.objects.create() + c2 = Car.objects.create() + d1 = Driver.objects.create() + self.assertIs(d1.car, None) + with self.assertNumQueries(0): + self.assertEqual(list(c1.drivers.all()), []) diff --git a/tests/regressiontests/inspectdb/__init__.py b/tests/max_lengths/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/inspectdb/__init__.py +++ b/tests/max_lengths/__init__.py diff --git a/tests/regressiontests/max_lengths/models.py b/tests/max_lengths/models.py index d66e833e8c..d66e833e8c 100644 --- a/tests/regressiontests/max_lengths/models.py +++ b/tests/max_lengths/models.py diff --git a/tests/regressiontests/max_lengths/tests.py b/tests/max_lengths/tests.py index 9dfcabff45..9dfcabff45 100644 --- a/tests/regressiontests/max_lengths/tests.py +++ b/tests/max_lengths/tests.py diff --git a/tests/regressiontests/defaultfilters/__init__.py b/tests/middleware/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/defaultfilters/__init__.py +++ b/tests/middleware/__init__.py diff --git a/tests/regressiontests/middleware/cond_get_urls.py b/tests/middleware/cond_get_urls.py index 1dadd0d31a..1dadd0d31a 100644 --- a/tests/regressiontests/middleware/cond_get_urls.py +++ b/tests/middleware/cond_get_urls.py diff --git a/tests/regressiontests/middleware/extra_urls.py b/tests/middleware/extra_urls.py index 1aee48f7be..1aee48f7be 100644 --- a/tests/regressiontests/middleware/extra_urls.py +++ b/tests/middleware/extra_urls.py diff --git a/tests/middleware/models.py b/tests/middleware/models.py new file mode 100644 index 0000000000..7088bfc2f3 --- /dev/null +++ b/tests/middleware/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.utils.encoding import python_2_unicode_compatible + + +@python_2_unicode_compatible +class Band(models.Model): + name = models.CharField(max_length=100) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name diff --git a/tests/regressiontests/middleware/tests.py b/tests/middleware/tests.py index b8cffd9c92..f2f7f4df66 100644 --- a/tests/regressiontests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -1,36 +1,35 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals import gzip -import re -import random from io import BytesIO +import random +import re +import warnings from django.conf import settings from django.core import mail -from django.http import HttpRequest -from django.http import HttpResponse, StreamingHttpResponse +from django.db import (transaction, connections, DEFAULT_DB_ALIAS, + IntegrityError) +from django.http import HttpRequest, HttpResponse, StreamingHttpResponse from django.middleware.clickjacking import XFrameOptionsMiddleware -from django.middleware.common import CommonMiddleware +from django.middleware.common import CommonMiddleware, BrokenLinkEmailsMiddleware from django.middleware.http import ConditionalGetMiddleware from django.middleware.gzip import GZipMiddleware -from django.test import TestCase, RequestFactory +from django.middleware.transaction import TransactionMiddleware +from django.test import TransactionTestCase, TestCase, RequestFactory from django.test.utils import override_settings from django.utils import six +from django.utils.encoding import force_str from django.utils.six.moves import xrange +from django.utils.unittest import expectedFailure +from transactions.tests import IgnorePendingDeprecationWarningsMixin -class CommonMiddlewareTest(TestCase): - def setUp(self): - self.append_slash = settings.APPEND_SLASH - self.prepend_www = settings.PREPEND_WWW - self.ignorable_404_urls = settings.IGNORABLE_404_URLS - self.send_broken_email_links = settings.SEND_BROKEN_LINK_EMAILS +from .models import Band - def tearDown(self): - settings.APPEND_SLASH = self.append_slash - settings.PREPEND_WWW = self.prepend_www - settings.IGNORABLE_404_URLS = self.ignorable_404_urls - settings.SEND_BROKEN_LINK_EMAILS = self.send_broken_email_links + +class CommonMiddlewareTest(TestCase): def _get_request(self, path): request = HttpRequest() @@ -41,257 +40,244 @@ class CommonMiddlewareTest(TestCase): request.path = request.path_info = "/middleware/%s" % path return request + @override_settings(APPEND_SLASH=True) def test_append_slash_have_slash(self): """ Tests that URLs with slashes go unmolested. """ - settings.APPEND_SLASH = True request = self._get_request('slash/') self.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(r.status_code, 301) - self.assertEqual(r['Location'], 'http://testserver/middleware/slash/') + self.assertEqual(r.url, 'http://testserver/middleware/slash/') + @override_settings(APPEND_SLASH=True, DEBUG=True) 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: + with six.assertRaisesRegex(self, RuntimeError, 'end in a slash'): CommonMiddleware().process_request(request) - except RuntimeError as e: - self.assertTrue('end in a slash' in str(e)) - settings.DEBUG = False + @override_settings(APPEND_SLASH=False) def test_append_slash_disabled(self): """ Tests disabling append slash functionality. """ - settings.APPEND_SLASH = False request = self._get_request('slash') self.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(r.status_code, 301) self.assertEqual( - r['Location'], + r.url, 'http://testserver/middleware/needsquoting%23/') + @override_settings(APPEND_SLASH=False, PREPEND_WWW=True) def test_prepend_www(self): - settings.PREPEND_WWW = True - settings.APPEND_SLASH = False request = self._get_request('path/') r = CommonMiddleware().process_request(request) self.assertEqual(r.status_code, 301) self.assertEqual( - r['Location'], + r.url, 'http://www.testserver/middleware/path/') + @override_settings(APPEND_SLASH=True, PREPEND_WWW=True) 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.assertEqual(r.status_code, 301) - self.assertEqual(r['Location'], + self.assertEqual(r.url, 'http://www.testserver/middleware/slash/') + @override_settings(APPEND_SLASH=True, PREPEND_WWW=True) 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.assertEqual(r.status_code, 301) - self.assertEqual(r['Location'], + self.assertEqual(r.url, 'http://www.testserver/middleware/slash/') # The following tests examine expected behavior given a custom urlconf that # overrides the default one through the request object. + @override_settings(APPEND_SLASH=True) 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.assertEqual(CommonMiddleware().process_request(request), None) + """ + Tests that URLs with slashes go unmolested. + """ + request = self._get_request('customurlconf/slash/') + request.urlconf = 'middleware.extra_urls' + self.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(CommonMiddleware().process_request(request), None) + """ + Tests that matches to explicit slashless URLs go unmolested. + """ + request = self._get_request('customurlconf/noslash') + request.urlconf = 'middleware.extra_urls' + self.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(CommonMiddleware().process_request(request), None) + """ + Tests that APPEND_SLASH doesn't redirect to unknown resources. + """ + request = self._get_request('customurlconf/unknown') + request.urlconf = 'middleware.extra_urls' + self.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(r.status_code, 301) - self.assertEqual(r['Location'], 'http://testserver/middleware/customurlconf/slash/') + """ + Tests that APPEND_SLASH redirects slashless URLs to a valid pattern. + """ + request = self._get_request('customurlconf/slash') + request.urlconf = 'middleware.extra_urls' + r = CommonMiddleware().process_request(request) + self.assertFalse(r is None, + "CommonMiddlware failed to return APPEND_SLASH redirect using request.urlconf") + self.assertEqual(r.status_code, 301) + self.assertEqual(r.url, 'http://testserver/middleware/customurlconf/slash/') + @override_settings(APPEND_SLASH=True, DEBUG=True) 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 as e: - self.assertTrue('end in a slash' in str(e)) - settings.DEBUG = False + """ + 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. + """ + request = self._get_request('customurlconf/slash') + request.urlconf = 'middleware.extra_urls' + request.method = 'POST' + with six.assertRaisesRegex(self, RuntimeError, 'end in a slash'): + CommonMiddleware().process_request(request) + @override_settings(APPEND_SLASH=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.assertEqual(CommonMiddleware().process_request(request), None) + """ + Tests disabling append slash functionality. + """ + request = self._get_request('customurlconf/slash') + request.urlconf = 'middleware.extra_urls' + self.assertEqual(CommonMiddleware().process_request(request), None) + @override_settings(APPEND_SLASH=True) 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.assertEqual(r.status_code, 301) - self.assertEqual( - r['Location'], - 'http://testserver/middleware/customurlconf/needsquoting%23/') + """ + Tests that URLs which require quoting are redirected to their slash + version ok. + """ + request = self._get_request('customurlconf/needsquoting#') + request.urlconf = 'middleware.extra_urls' + r = CommonMiddleware().process_request(request) + self.assertFalse(r is None, + "CommonMiddlware failed to return APPEND_SLASH redirect using request.urlconf") + self.assertEqual(r.status_code, 301) + self.assertEqual( + r.url, + 'http://testserver/middleware/customurlconf/needsquoting%23/') + @override_settings(APPEND_SLASH=False, PREPEND_WWW=True) 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.assertEqual(r.status_code, 301) - self.assertEqual( - r['Location'], - 'http://www.testserver/middleware/customurlconf/path/') + request = self._get_request('customurlconf/path/') + request.urlconf = 'middleware.extra_urls' + r = CommonMiddleware().process_request(request) + self.assertEqual(r.status_code, 301) + self.assertEqual( + r.url, + 'http://www.testserver/middleware/customurlconf/path/') + @override_settings(APPEND_SLASH=True, PREPEND_WWW=True) 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.assertEqual(r.status_code, 301) - self.assertEqual(r['Location'], - 'http://www.testserver/middleware/customurlconf/slash/') + request = self._get_request('customurlconf/slash/') + request.urlconf = 'middleware.extra_urls' + r = CommonMiddleware().process_request(request) + self.assertEqual(r.status_code, 301) + self.assertEqual(r.url, + 'http://www.testserver/middleware/customurlconf/slash/') + @override_settings(APPEND_SLASH=True, PREPEND_WWW=True) 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.assertEqual(r.status_code, 301) - self.assertEqual(r['Location'], - 'http://www.testserver/middleware/customurlconf/slash/') + request = self._get_request('customurlconf/slash') + request.urlconf = 'middleware.extra_urls' + r = CommonMiddleware().process_request(request) + self.assertEqual(r.status_code, 301) + self.assertEqual(r.url, + 'http://www.testserver/middleware/customurlconf/slash/') - # Tests for the 404 error reporting via email + # Legacy tests for the 404 error reporting via email (to be removed in 1.8) + @override_settings(IGNORABLE_404_URLS=(re.compile(r'foo'),), + SEND_BROKEN_LINK_EMAILS=True, + MANAGERS=('PHB@dilbert.com',)) def test_404_error_reporting(self): - settings.IGNORABLE_404_URLS = (re.compile(r'foo'),) - settings.SEND_BROKEN_LINK_EMAILS = True request = self._get_request('regular_url/that/does/not/exist') request.META['HTTP_REFERER'] = '/another/url/' - response = self.client.get(request.path) - CommonMiddleware().process_response(request, response) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", PendingDeprecationWarning) + response = self.client.get(request.path) + CommonMiddleware().process_response(request, response) self.assertEqual(len(mail.outbox), 1) self.assertIn('Broken', mail.outbox[0].subject) + @override_settings(IGNORABLE_404_URLS=(re.compile(r'foo'),), + SEND_BROKEN_LINK_EMAILS=True, + MANAGERS=('PHB@dilbert.com',)) def test_404_error_reporting_no_referer(self): - settings.IGNORABLE_404_URLS = (re.compile(r'foo'),) - settings.SEND_BROKEN_LINK_EMAILS = True request = self._get_request('regular_url/that/does/not/exist') - response = self.client.get(request.path) - CommonMiddleware().process_response(request, response) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", PendingDeprecationWarning) + response = self.client.get(request.path) + CommonMiddleware().process_response(request, response) self.assertEqual(len(mail.outbox), 0) + @override_settings(IGNORABLE_404_URLS=(re.compile(r'foo'),), + SEND_BROKEN_LINK_EMAILS=True, + MANAGERS=('PHB@dilbert.com',)) def test_404_error_reporting_ignored_url(self): - settings.IGNORABLE_404_URLS = (re.compile(r'foo'),) - settings.SEND_BROKEN_LINK_EMAILS = True request = self._get_request('foo_url/that/does/not/exist/either') request.META['HTTP_REFERER'] = '/another/url/' - response = self.client.get(request.path) - CommonMiddleware().process_response(request, response) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", PendingDeprecationWarning) + response = self.client.get(request.path) + CommonMiddleware().process_response(request, response) self.assertEqual(len(mail.outbox), 0) # Other tests @@ -299,13 +285,44 @@ class CommonMiddlewareTest(TestCase): def test_non_ascii_query_string_does_not_crash(self): """Regression test for #15152""" request = self._get_request('slash') - request.META['QUERY_STRING'] = 'drink=café' + request.META['QUERY_STRING'] = force_str('drink=café') response = CommonMiddleware().process_request(request) self.assertEqual(response.status_code, 301) +@override_settings( + IGNORABLE_404_URLS=(re.compile(r'foo'),), + MANAGERS=('PHB@dilbert.com',), +) +class BrokenLinkEmailsMiddlewareTest(TestCase): + + def setUp(self): + self.req = HttpRequest() + self.req.META = { + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': 80, + } + self.req.path = self.req.path_info = 'regular_url/that/does/not/exist' + self.resp = self.client.get(self.req.path) + + def test_404_error_reporting(self): + self.req.META['HTTP_REFERER'] = '/another/url/' + BrokenLinkEmailsMiddleware().process_response(self.req, self.resp) + self.assertEqual(len(mail.outbox), 1) + self.assertIn('Broken', mail.outbox[0].subject) + + def test_404_error_reporting_no_referer(self): + BrokenLinkEmailsMiddleware().process_response(self.req, self.resp) + self.assertEqual(len(mail.outbox), 0) + + def test_404_error_reporting_ignored_url(self): + self.req.path = self.req.path_info = 'foo_url/that/does/not/exist' + BrokenLinkEmailsMiddleware().process_response(self.req, self.resp) + self.assertEqual(len(mail.outbox), 0) + + class ConditionalGetMiddlewareTest(TestCase): - urls = 'regressiontests.middleware.cond_get_urls' + urls = 'middleware.cond_get_urls' def setUp(self): self.req = HttpRequest() self.req.META = { @@ -424,70 +441,66 @@ class XFrameOptionsMiddlewareTest(TestCase): """ Tests for the X-Frame-Options clickjacking prevention middleware. """ - def setUp(self): - self.x_frame_options = settings.X_FRAME_OPTIONS - - def tearDown(self): - settings.X_FRAME_OPTIONS = self.x_frame_options def test_same_origin(self): """ Tests that the X_FRAME_OPTIONS setting can be set to SAMEORIGIN to have the middleware use that value for the HTTP header. """ - settings.X_FRAME_OPTIONS = 'SAMEORIGIN' - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + with override_settings(X_FRAME_OPTIONS='SAMEORIGIN'): + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + HttpResponse()) + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') - settings.X_FRAME_OPTIONS = 'sameorigin' - r = XFrameOptionsMiddleware().process_response(HttpRequest(), + with override_settings(X_FRAME_OPTIONS='sameorigin'): + r = XFrameOptionsMiddleware().process_response(HttpRequest(), HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') def test_deny(self): """ Tests that the X_FRAME_OPTIONS setting can be set to DENY to have the middleware use that value for the HTTP header. """ - settings.X_FRAME_OPTIONS = 'DENY' - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'DENY') + with override_settings(X_FRAME_OPTIONS='DENY'): + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + HttpResponse()) + self.assertEqual(r['X-Frame-Options'], 'DENY') - settings.X_FRAME_OPTIONS = 'deny' - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'DENY') + with override_settings(X_FRAME_OPTIONS='deny'): + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + HttpResponse()) + self.assertEqual(r['X-Frame-Options'], 'DENY') def test_defaults_sameorigin(self): """ Tests that if the X_FRAME_OPTIONS setting is not set then it defaults to SAMEORIGIN. """ - del settings.X_FRAME_OPTIONS - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + with override_settings(X_FRAME_OPTIONS=None): + del settings.X_FRAME_OPTIONS # restored by override_settings + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + HttpResponse()) + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') def test_dont_set_if_set(self): """ Tests that if the X-Frame-Options header is already set then the middleware does not attempt to override it. """ - settings.X_FRAME_OPTIONS = 'DENY' - response = HttpResponse() - response['X-Frame-Options'] = 'SAMEORIGIN' - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - response) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + with override_settings(X_FRAME_OPTIONS='DENY'): + response = HttpResponse() + response['X-Frame-Options'] = 'SAMEORIGIN' + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + response) + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') - settings.X_FRAME_OPTIONS = 'SAMEORIGIN' - response = HttpResponse() - response['X-Frame-Options'] = 'DENY' - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - response) - self.assertEqual(r['X-Frame-Options'], 'DENY') + with override_settings(X_FRAME_OPTIONS='SAMEORIGIN'): + response = HttpResponse() + response['X-Frame-Options'] = 'DENY' + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + response) + self.assertEqual(r['X-Frame-Options'], 'DENY') def test_response_exempt(self): """ @@ -495,18 +508,18 @@ class XFrameOptionsMiddlewareTest(TestCase): to False then it still sets the header, but if it's set to True then it does not. """ - settings.X_FRAME_OPTIONS = 'SAMEORIGIN' - response = HttpResponse() - response.xframe_options_exempt = False - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - response) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + with override_settings(X_FRAME_OPTIONS='SAMEORIGIN'): + response = HttpResponse() + response.xframe_options_exempt = False + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + response) + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') - response = HttpResponse() - response.xframe_options_exempt = True - r = XFrameOptionsMiddleware().process_response(HttpRequest(), - response) - self.assertEqual(r.get('X-Frame-Options', None), None) + response = HttpResponse() + response.xframe_options_exempt = True + r = XFrameOptionsMiddleware().process_response(HttpRequest(), + response) + self.assertEqual(r.get('X-Frame-Options', None), None) def test_is_extendable(self): """ @@ -523,23 +536,23 @@ class XFrameOptionsMiddlewareTest(TestCase): return 'SAMEORIGIN' return 'DENY' - settings.X_FRAME_OPTIONS = 'DENY' - response = HttpResponse() - response.sameorigin = True - r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(), - response) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + with override_settings(X_FRAME_OPTIONS='DENY'): + response = HttpResponse() + response.sameorigin = True + r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(), + response) + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') - request = HttpRequest() - request.sameorigin = True - r = OtherXFrameOptionsMiddleware().process_response(request, - HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') + request = HttpRequest() + request.sameorigin = True + r = OtherXFrameOptionsMiddleware().process_response(request, + HttpResponse()) + self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN') - settings.X_FRAME_OPTIONS = 'SAMEORIGIN' - r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(), - HttpResponse()) - self.assertEqual(r['X-Frame-Options'], 'DENY') + with override_settings(X_FRAME_OPTIONS='SAMEORIGIN'): + r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(), + HttpResponse()) + self.assertEqual(r['X-Frame-Options'], 'DENY') class GZipMiddlewareTest(TestCase): @@ -664,3 +677,60 @@ class ETagGZipMiddlewareTest(TestCase): nogzip_etag = response.get('ETag') self.assertNotEqual(gzip_etag, nogzip_etag) + +class TransactionMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): + """ + Test the transaction middleware. + """ + def setUp(self): + super(TransactionMiddlewareTest, self).setUp() + self.request = HttpRequest() + self.request.META = { + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': 80, + } + self.request.path = self.request.path_info = "/" + self.response = HttpResponse() + self.response.status_code = 200 + + def tearDown(self): + transaction.abort() + super(TransactionMiddlewareTest, self).tearDown() + + def test_request(self): + TransactionMiddleware().process_request(self.request) + self.assertFalse(transaction.get_autocommit()) + + def test_managed_response(self): + transaction.enter_transaction_management() + Band.objects.create(name='The Beatles') + self.assertTrue(transaction.is_dirty()) + TransactionMiddleware().process_response(self.request, self.response) + self.assertFalse(transaction.is_dirty()) + self.assertEqual(Band.objects.count(), 1) + + def test_exception(self): + transaction.enter_transaction_management() + Band.objects.create(name='The Beatles') + self.assertTrue(transaction.is_dirty()) + TransactionMiddleware().process_exception(self.request, None) + self.assertFalse(transaction.is_dirty()) + self.assertEqual(Band.objects.count(), 0) + + def test_failing_commit(self): + # It is possible that connection.commit() fails. Check that + # TransactionMiddleware handles such cases correctly. + try: + def raise_exception(): + raise IntegrityError() + connections[DEFAULT_DB_ALIAS].commit = raise_exception + transaction.enter_transaction_management() + Band.objects.create(name='The Beatles') + self.assertTrue(transaction.is_dirty()) + with self.assertRaises(IntegrityError): + TransactionMiddleware().process_response(self.request, None) + self.assertFalse(transaction.is_dirty()) + self.assertEqual(Band.objects.count(), 0) + self.assertFalse(transaction.is_managed()) + finally: + del connections[DEFAULT_DB_ALIAS].commit diff --git a/tests/regressiontests/middleware/urls.py b/tests/middleware/urls.py index 217e3ae034..217e3ae034 100644 --- a/tests/regressiontests/middleware/urls.py +++ b/tests/middleware/urls.py diff --git a/tests/regressiontests/defer_regress/__init__.py b/tests/middleware_exceptions/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/defer_regress/__init__.py +++ b/tests/middleware_exceptions/__init__.py diff --git a/tests/regressiontests/middleware_exceptions/models.py b/tests/middleware_exceptions/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/middleware_exceptions/models.py +++ b/tests/middleware_exceptions/models.py diff --git a/tests/regressiontests/middleware_exceptions/tests.py b/tests/middleware_exceptions/tests.py index 586aa0f004..18f8cd1ba3 100644 --- a/tests/regressiontests/middleware_exceptions/tests.py +++ b/tests/middleware_exceptions/tests.py @@ -94,7 +94,7 @@ class BadExceptionMiddleware(TestMiddleware): class BaseMiddlewareExceptionTest(TestCase): - urls = 'regressiontests.middleware_exceptions.urls' + urls = 'middleware_exceptions.urls' def setUp(self): self.exceptions = [] @@ -374,7 +374,7 @@ class MiddlewareTests(BaseMiddlewareExceptionTest): self._add_middleware(middleware) self._add_middleware(pre_middleware) self.assert_exceptions_handled('/middleware_exceptions/null_view/', [ - "The view regressiontests.middleware_exceptions.views.null_view didn't return an HttpResponse object.", + "The view middleware_exceptions.views.null_view didn't return an HttpResponse object.", ], ValueError()) @@ -391,7 +391,7 @@ class MiddlewareTests(BaseMiddlewareExceptionTest): self._add_middleware(middleware) self._add_middleware(pre_middleware) self.assert_exceptions_handled('/middleware_exceptions/null_view/', [ - "The view regressiontests.middleware_exceptions.views.null_view didn't return an HttpResponse object." + "The view middleware_exceptions.views.null_view didn't return an HttpResponse object." ], ValueError()) @@ -685,7 +685,7 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest): self._add_middleware(bad_middleware) self._add_middleware(pre_middleware) self.assert_exceptions_handled('/middleware_exceptions/null_view/', [ - "The view regressiontests.middleware_exceptions.views.null_view didn't return an HttpResponse object.", + "The view middleware_exceptions.views.null_view didn't return an HttpResponse object.", 'Test Response Exception' ]) @@ -702,7 +702,7 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest): self._add_middleware(bad_middleware) self._add_middleware(pre_middleware) self.assert_exceptions_handled('/middleware_exceptions/null_view/', [ - "The view regressiontests.middleware_exceptions.views.null_view didn't return an HttpResponse object." + "The view middleware_exceptions.views.null_view didn't return an HttpResponse object." ], ValueError()) @@ -770,7 +770,7 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest): _missing = object() class RootUrlconfTests(TestCase): - urls = 'regressiontests.middleware_exceptions.urls' + urls = 'middleware_exceptions.urls' def test_missing_root_urlconf(self): try: diff --git a/tests/regressiontests/middleware_exceptions/urls.py b/tests/middleware_exceptions/urls.py index 042607fdc8..042607fdc8 100644 --- a/tests/regressiontests/middleware_exceptions/urls.py +++ b/tests/middleware_exceptions/urls.py diff --git a/tests/regressiontests/middleware_exceptions/views.py b/tests/middleware_exceptions/views.py index ddf28c46a3..ddf28c46a3 100644 --- a/tests/regressiontests/middleware_exceptions/views.py +++ b/tests/middleware_exceptions/views.py diff --git a/tests/regressiontests/model_fields/4x8.png b/tests/model_fields/4x8.png Binary files differindex ffce444d48..ffce444d48 100644 --- a/tests/regressiontests/model_fields/4x8.png +++ b/tests/model_fields/4x8.png diff --git a/tests/regressiontests/model_fields/8x4.png b/tests/model_fields/8x4.png Binary files differindex 60e6d69ee1..60e6d69ee1 100644 --- a/tests/regressiontests/model_fields/8x4.png +++ b/tests/model_fields/8x4.png diff --git a/tests/regressiontests/expressions_regress/__init__.py b/tests/model_fields/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/expressions_regress/__init__.py +++ b/tests/model_fields/__init__.py diff --git a/tests/regressiontests/model_fields/models.py b/tests/model_fields/models.py index 1d20f44fae..c3b2f7fccb 100644 --- a/tests/regressiontests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -106,6 +106,10 @@ class VerboseNameField(models.Model): class DecimalLessThanOne(models.Model): d = models.DecimalField(max_digits=3, decimal_places=3) +class DataModel(models.Model): + short_data = models.BinaryField(max_length=10, default=b'\x08') + data = models.BinaryField() + ############################################################################### # FileField diff --git a/tests/regressiontests/model_fields/imagefield.py b/tests/model_fields/test_imagefield.py index df0215db3d..df0215db3d 100644 --- a/tests/regressiontests/model_fields/imagefield.py +++ b/tests/model_fields/test_imagefield.py diff --git a/tests/regressiontests/model_fields/tests.py b/tests/model_fields/tests.py index a49894e36c..9af8325040 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -6,16 +6,16 @@ from decimal import Decimal from django import test from django import forms from django.core.exceptions import ValidationError -from django.db import models +from django.db import models, IntegrityError from django.db.models.fields.files import FieldFile from django.utils import six from django.utils import unittest from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, - NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField, - FksToBooleans) + NullBooleanModel, BooleanModel, DataModel, Document, RenamedField, + VerboseNameField, FksToBooleans) -from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests, +from .test_imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests, TwoImageFieldTests, ImageFieldNoDimensionsTests, ImageFieldOneDimensionTests, ImageFieldDimensionsFirstTests, ImageFieldUsingFileTests) @@ -73,6 +73,16 @@ class BasicFieldTests(test.TestCase): self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk') + def test_formclass_with_choices(self): + # regression for 18162 + class CustomChoiceField(forms.TypedChoiceField): + pass + choices = [('a@b.cc', 'a@b.cc'), ('b@b.cc', 'b@b.cc')] + field = models.CharField(choices=choices) + klass = CustomChoiceField + self.assertIsInstance(field.formfield(form_class=klass), klass) + + class DecimalFieldTests(test.TestCase): def test_to_python(self): f = models.DecimalField(max_digits=4, decimal_places=2) @@ -255,6 +265,18 @@ class BooleanFieldTests(unittest.TestCase): self.assertEqual(mc.bf.bfield, False) self.assertEqual(mc.nbf.nbfield, False) + def test_null_default(self): + """ + Check that a BooleanField defaults to None -- which isn't + a valid value (#15124). + """ + b = BooleanModel() + self.assertIsNone(b.bfield) + with self.assertRaises(IntegrityError): + b.save() + nb = NullBooleanModel() + self.assertIsNone(nb.nbfield) + nb.save() # no error class ChoicesTests(test.TestCase): def test_choices_and_field_display(self): @@ -414,3 +436,25 @@ class FileFieldTests(unittest.TestCase): field = d._meta.get_field('myfile') field.save_form_data(d, 'else.txt') self.assertEqual(d.myfile, 'else.txt') + + +class BinaryFieldTests(test.TestCase): + binary_data = b'\x00\x46\xFE' + + def test_set_and_retrieve(self): + data_set = (self.binary_data, six.memoryview(self.binary_data)) + for bdata in data_set: + dm = DataModel(data=bdata) + dm.save() + dm = DataModel.objects.get(pk=dm.pk) + self.assertEqual(bytes(dm.data), bytes(bdata)) + # Resave (=update) + dm.save() + dm = DataModel.objects.get(pk=dm.pk) + self.assertEqual(bytes(dm.data), bytes(bdata)) + # Test default value + self.assertEqual(bytes(dm.short_data), b'\x08') + + def test_max_length(self): + dm = DataModel(short_data=self.binary_data*4) + self.assertRaises(ValidationError, dm.full_clean) diff --git a/tests/regressiontests/extra_regress/__init__.py b/tests/model_forms/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/extra_regress/__init__.py +++ b/tests/model_forms/__init__.py diff --git a/tests/modeltests/model_forms/models.py b/tests/model_forms/models.py index 132da2d318..25c780f1c2 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/model_forms/models.py @@ -16,6 +16,7 @@ from django.db import models from django.utils import six from django.utils.encoding import python_2_unicode_compatible +from shared_models.models import Author, Book temp_storage_dir = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) temp_storage = FileSystemStorage(temp_storage_dir) @@ -45,22 +46,12 @@ class Category(models.Model): return self.__str__() @python_2_unicode_compatible -class Writer(models.Model): - name = models.CharField(max_length=50, help_text='Use both first and last names.') - - class Meta: - ordering = ('name',) - - def __str__(self): - return self.name - -@python_2_unicode_compatible class Article(models.Model): headline = models.CharField(max_length=50) slug = models.SlugField() pub_date = models.DateField() created = models.DateField(editable=False) - writer = models.ForeignKey(Writer) + writer = models.ForeignKey(Author) article = models.TextField() categories = models.ManyToManyField(Category, blank=True) status = models.PositiveIntegerField(choices=ARTICLE_STATUS, blank=True, null=True) @@ -80,12 +71,12 @@ class ImprovedArticle(models.Model): class ImprovedArticleWithParentLink(models.Model): article = models.OneToOneField(Article, parent_link=True) -class BetterWriter(Writer): +class BetterAuthor(Author): score = models.IntegerField() @python_2_unicode_compatible -class WriterProfile(models.Model): - writer = models.OneToOneField(Writer, primary_key=True) +class AuthorProfile(models.Model): + writer = models.OneToOneField(Author, primary_key=True) age = models.PositiveIntegerField() def __str__(self): @@ -192,14 +183,6 @@ class Inventory(models.Model): def __repr__(self): return self.__str__() -class Book(models.Model): - title = models.CharField(max_length=40) - author = models.ForeignKey(Writer, blank=True, null=True) - special_id = models.IntegerField(blank=True, null=True, unique=True) - - class Meta: - unique_together = ('title', 'author') - class BookXtra(models.Model): isbn = models.CharField(max_length=16, unique=True) suffix1 = models.IntegerField(blank=True, default=0) @@ -263,3 +246,18 @@ class FlexibleDatePost(models.Model): slug = models.CharField(max_length=50, unique_for_year='posted', blank=True) subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True) posted = models.DateField(blank=True, null=True) + +@python_2_unicode_compatible +class Colour(models.Model): + name = models.CharField(max_length=50) + + def __iter__(self): + for number in xrange(5): + yield number + + def __str__(self): + return self.name + +class ColourfulItem(models.Model): + name = models.CharField(max_length=50) + colours = models.ManyToManyField(Colour) diff --git a/tests/regressiontests/file_storage/test.png b/tests/model_forms/test.png Binary files differindex 4f17cd075d..4f17cd075d 100644 --- a/tests/regressiontests/file_storage/test.png +++ b/tests/model_forms/test.png diff --git a/tests/modeltests/model_forms/test2.png b/tests/model_forms/test2.png Binary files differindex 10702f75a1..10702f75a1 100644 --- a/tests/modeltests/model_forms/test2.png +++ b/tests/model_forms/test2.png diff --git a/tests/modeltests/model_forms/tests.py b/tests/model_forms/tests.py index 9699b155c0..96c3ecbdce 100644 --- a/tests/modeltests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -5,6 +5,7 @@ import os from decimal import Decimal from django import forms +from django.core.exceptions import FieldError from django.core.files.uploadedfile import SimpleUploadedFile from django.core.validators import ValidationError from django.db import connection @@ -15,11 +16,14 @@ from django.utils.unittest import skipUnless from django.test import TestCase from django.utils import six -from .models import (Article, ArticleStatus, BetterWriter, BigInt, Book, +from shared_models.models import Author, Book + +from .models import (Article, ArticleStatus, BetterAuthor, BigInt, Category, CommaSeparatedInteger, CustomFieldForExclusionModel, DerivedBook, DerivedPost, ExplicitPK, FlexibleDatePost, ImprovedArticle, ImprovedArticleWithParentLink, Inventory, Post, Price, - Product, TextFile, Writer, WriterProfile, test_images) + Product, TextFile, AuthorProfile, Colour, ColourfulItem, + test_images) if test_images: from .models import ImageFile, OptionalImageFile @@ -43,11 +47,13 @@ class PriceForm(forms.ModelForm): class BookForm(forms.ModelForm): class Meta: - model = Book + fields = ['title', 'author', 'pubdate'] + model = Book class DerivedBookForm(forms.ModelForm): class Meta: + fields = ['title', 'author', 'isbn', 'suffix1', 'suffix2'] model = DerivedBook @@ -67,11 +73,11 @@ class DerivedPostForm(forms.ModelForm): model = DerivedPost -class CustomWriterForm(forms.ModelForm): +class CustomAuthorForm(forms.ModelForm): name = forms.CharField(required=False) class Meta: - model = Writer + model = Author class FlexDatePostForm(forms.ModelForm): @@ -100,7 +106,7 @@ class PartialArticleForm(forms.ModelForm): class RoykoForm(forms.ModelForm): class Meta: - model = Writer + model = Author class TestArticleForm(forms.ModelForm): class Meta: @@ -132,6 +138,9 @@ class ShortCategory(forms.ModelForm): slug = forms.CharField(max_length=5) url = forms.CharField(max_length=3) + class Meta: + model = Category + class ImprovedArticleForm(forms.ModelForm): class Meta: model = ImprovedArticle @@ -140,13 +149,13 @@ class ImprovedArticleWithParentLinkForm(forms.ModelForm): class Meta: model = ImprovedArticleWithParentLink -class BetterWriterForm(forms.ModelForm): +class BetterAuthorForm(forms.ModelForm): class Meta: - model = BetterWriter + model = BetterAuthor -class WriterProfileForm(forms.ModelForm): +class AuthorProfileForm(forms.ModelForm): class Meta: - model = WriterProfile + model = AuthorProfile class TextFileForm(forms.ModelForm): class Meta: @@ -174,6 +183,10 @@ class PriceFormWithoutQuantity(forms.ModelForm): model = Price exclude = ('quantity',) +class ColourfulItemForm(forms.ModelForm): + class Meta: + model = ColourfulItem + class ModelFormBaseTest(TestCase): def test_base_form(self): @@ -198,13 +211,13 @@ class ModelFormBaseTest(TestCase): forms.fields.BooleanField)) def test_override_field(self): - class WriterForm(forms.ModelForm): + class AuthorForm(forms.ModelForm): book = forms.CharField(required=False) class Meta: - model = Writer + model = Author - wf = WriterForm({'name': 'Richard Lockridge'}) + wf = AuthorForm({'name': 'Richard Lockridge'}) self.assertTrue(wf.is_valid()) def test_limit_fields(self): @@ -216,6 +229,22 @@ class ModelFormBaseTest(TestCase): self.assertEqual(list(LimitFields.base_fields), ['url']) + def test_limit_nonexistent_field(self): + expected_msg = 'Unknown field(s) (nonexistent) specified for Category' + with self.assertRaisesMessage(FieldError, expected_msg): + class InvalidCategoryForm(forms.ModelForm): + class Meta: + model = Category + fields = ['nonexistent'] + + def test_limit_fields_with_string(self): + expected_msg = "CategoryForm.Meta.fields cannot be a string. Did you mean to type: ('url',)?" + with self.assertRaisesMessage(TypeError, expected_msg): + class CategoryForm(forms.ModelForm): + class Meta: + model = Category + fields = ('url') # note the missing comma + def test_exclude_fields(self): class ExcludeFields(forms.ModelForm): class Meta: @@ -225,6 +254,23 @@ class ModelFormBaseTest(TestCase): self.assertEqual(list(ExcludeFields.base_fields), ['name', 'slug']) + def test_exclude_nonexistent_field(self): + class ExcludeFields(forms.ModelForm): + class Meta: + model = Category + exclude = ['nonexistent'] + + self.assertEqual(list(ExcludeFields.base_fields), + ['name', 'slug', 'url']) + + def test_exclude_fields_with_string(self): + expected_msg = "CategoryForm.Meta.exclude cannot be a string. Did you mean to type: ('url',)?" + with self.assertRaisesMessage(TypeError, expected_msg): + class CategoryForm(forms.ModelForm): + class Meta: + model = Category + exclude = ('url') # note the missing comma + def test_confused_form(self): class ConfusedForm(forms.ModelForm): """ Using 'fields' *and* 'exclude'. Not sure why you'd want to do @@ -272,6 +318,19 @@ class ModelFormBaseTest(TestCase): ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] ) + def test_invalid_meta_model(self): + class InvalidModelForm(forms.ModelForm): + class Meta: + pass # no model + + # Can't create new form + with self.assertRaises(ValueError): + f = InvalidModelForm() + + # Even if you provide a model instance + with self.assertRaises(ValueError): + f = InvalidModelForm(instance=Category) + def test_subcategory_form(self): class SubCategoryForm(BaseCategoryForm): """ Subclassing without specifying a Meta on the class will use @@ -390,7 +449,7 @@ class ValidationTest(TestCase): assert form.is_valid() def test_notrequired_overrides_notblank(self): - form = CustomWriterForm({}) + form = CustomAuthorForm({}) assert form.is_valid() @@ -399,7 +458,7 @@ class ValidationTest(TestCase): # unique/unique_together validation class UniqueTest(TestCase): def setUp(self): - self.writer = Writer.objects.create(name='Mike Royko') + self.author = Author.objects.create(name='Mike Royko') def test_simple_unique(self): form = ProductForm({'slug': 'teddy-bear-blue'}) @@ -423,33 +482,31 @@ class UniqueTest(TestCase): def test_unique_null(self): title = 'I May Be Wrong But I Doubt It' - form = BookForm({'title': title, 'author': self.writer.pk}) + form = BookForm({'title': title, 'author': self.author.pk, 'pubdate': '2012-12-12 00:00:00'}) self.assertTrue(form.is_valid()) form.save() - form = BookForm({'title': title, 'author': self.writer.pk}) + form = BookForm({'title': title, 'author': self.author.pk, 'pubdate': '2012-12-12 00:00:00'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.']) - form = BookForm({'title': title}) + form = BookForm({'title': title, 'pubdate': '2012-12-12 00:00:00'}) self.assertTrue(form.is_valid()) form.save() - form = BookForm({'title': title}) + form = BookForm({'title': title, 'pubdate': '2012-12-12 00:00:00'}) self.assertTrue(form.is_valid()) def test_inherited_unique(self): - title = 'Boss' - Book.objects.create(title=title, author=self.writer, special_id=1) - form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'special_id': '1', 'isbn': '12345'}) + form = BetterAuthorForm({'name': 'Mike Royko', 'score': 3}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['special_id'], ['Book with this Special id already exists.']) + self.assertEqual(form.errors['name'], ['Author with this Name already exists.']) def test_inherited_unique_together(self): title = 'Boss' - form = BookForm({'title': title, 'author': self.writer.pk}) + form = BookForm({'title': title, 'author': self.author.pk, 'pubdate': '2012-12-12 00:00:00'}) self.assertTrue(form.is_valid()) form.save() - form = DerivedBookForm({'title': title, 'author': self.writer.pk, 'isbn': '12345'}) + form = DerivedBookForm({'title': title, 'author': self.author.pk, 'isbn': '12345'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.']) @@ -457,8 +514,9 @@ class UniqueTest(TestCase): def test_abstract_inherited_unique(self): title = 'Boss' isbn = '12345' - dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn) - form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'isbn': isbn}) + dbook = DerivedBook.objects.create(title=title, author=self.author, isbn=isbn, + pubdate='2012-12-12 00:00') + form = DerivedBookForm({'title': 'Other', 'author': self.author.pk, 'isbn': isbn}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['isbn'], ['Derived book with this Isbn already exists.']) @@ -466,10 +524,11 @@ class UniqueTest(TestCase): def test_abstract_inherited_unique_together(self): title = 'Boss' isbn = '12345' - dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn) + dbook = DerivedBook.objects.create(title=title, author=self.author, isbn=isbn, + pubdate='2012-12-12 00:00') form = DerivedBookForm({ 'title': 'Other', - 'author': self.writer.pk, + 'author': self.author.pk, 'isbn': '9876', 'suffix1': '0', 'suffix2': '0' @@ -570,7 +629,7 @@ class ModelToDictTests(TestCase): ] for c in categories: c.save() - writer = Writer(name='Test writer') + writer = Author(name='Test writer') writer.save() art = Article( @@ -679,10 +738,10 @@ class OldFormForXTests(TestCase): with self.assertRaises(ValueError): f.save() - # Create a couple of Writers. - w_royko = Writer(name='Mike Royko') + # Create a couple of Authors. + w_royko = Author(name='Mike Royko') w_royko.save() - w_woodward = Writer(name='Bob Woodward') + w_woodward = Author(name='Bob Woodward') w_woodward.save() # ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any # fields with the 'choices' attribute are represented by a ChoiceField. @@ -720,9 +779,9 @@ class OldFormForXTests(TestCase): # When the ModelForm is passed an instance, that instance's current values are # inserted as 'initial' data in each Field. - w = Writer.objects.get(name='Mike Royko') + w = Author.objects.get(name='Mike Royko') f = RoykoForm(auto_id=False, instance=w) - self.assertHTMLEqual(six.text_type(f), '''<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br /><span class="helptext">Use both first and last names.</span></td></tr>''') + self.assertHTMLEqual(six.text_type(f), '''<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="100" /><br /><span class="helptext">Use both first and last names.</span></td></tr>''') art = Article( headline='Test article', @@ -934,7 +993,7 @@ class OldFormForXTests(TestCase): c4 = Category.objects.create(name='Fourth', url='4th') self.assertEqual(c4.name, 'Fourth') - w_bernstein = Writer.objects.create(name='Carl Bernstein') + w_bernstein = Author.objects.create(name='Carl Bernstein') self.assertEqual(w_bernstein.name, 'Carl Bernstein') self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li> <li>Slug: <input type="text" name="slug" maxlength="50" /></li> @@ -968,6 +1027,7 @@ class OldFormForXTests(TestCase): (c2.pk, "It's a test"), (c3.pk, 'Third'), (c4.pk, 'Fourth')]) + self.assertEqual(5, len(f.choices)) with self.assertRaises(ValidationError): f.clean('') with self.assertRaises(ValidationError): @@ -1110,17 +1170,17 @@ class OldFormForXTests(TestCase): self.assertEqual(list(ImprovedArticleWithParentLinkForm.base_fields), []) - bw = BetterWriter(name='Joe Better', score=10) + bw = BetterAuthor(name='Joe Better', score=10) bw.save() self.assertEqual(sorted(model_to_dict(bw)), - ['id', 'name', 'score', 'writer_ptr']) + ['author_ptr', 'id', 'name', 'score']) - form = BetterWriterForm({'name': 'Some Name', 'score': 12}) + form = BetterAuthorForm({'name': 'Some Name', 'score': 12}) self.assertEqual(form.is_valid(), True) bw2 = form.save() bw2.delete() - form = WriterProfileForm() + form = AuthorProfileForm() self.assertHTMLEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer"> <option value="" selected="selected">---------</option> <option value="%s">Bob Woodward</option> @@ -1128,17 +1188,17 @@ class OldFormForXTests(TestCase): <option value="%s">Joe Better</option> <option value="%s">Mike Royko</option> </select></p> -<p><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) +<p><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) data = { 'writer': six.text_type(w_woodward.pk), 'age': '65', } - form = WriterProfileForm(data) + form = AuthorProfileForm(data) instance = form.save() self.assertEqual(six.text_type(instance), 'Bob Woodward is 65') - form = WriterProfileForm(instance=instance) + form = AuthorProfileForm(instance=instance) self.assertHTMLEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer"> <option value="">---------</option> <option value="%s" selected="selected">Bob Woodward</option> @@ -1146,7 +1206,7 @@ class OldFormForXTests(TestCase): <option value="%s">Joe Better</option> <option value="%s">Mike Royko</option> </select></p> -<p><label for="id_age">Age:</label> <input type="text" name="age" value="65" id="id_age" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) +<p><label for="id_age">Age:</label> <input type="number" name="age" value="65" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) def test_file_field(self): # Test conditions when files is either not given or empty. @@ -1518,3 +1578,15 @@ class OldFormForXTests(TestCase): ['name']) self.assertHTMLEqual(six.text_type(CustomFieldForExclusionForm()), '''<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="10" /></td></tr>''') + + def test_iterable_model_m2m(self) : + colour = Colour.objects.create(name='Blue') + form = ColourfulItemForm() + self.maxDiff = 1024 + self.assertHTMLEqual( + form.as_p(), + """<p><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="50" /></p> + <p><label for="id_colours">Colours:</label> <select multiple="multiple" name="colours" id="id_colours"> + <option value="%(blue_pk)s">Blue</option> + </select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>""" + % {'blue_pk': colour.pk}) diff --git a/tests/regressiontests/file_uploads/__init__.py b/tests/model_forms_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/file_uploads/__init__.py +++ b/tests/model_forms_regress/__init__.py diff --git a/tests/regressiontests/model_forms_regress/models.py b/tests/model_forms_regress/models.py index 2c2fd39158..2c2fd39158 100644 --- a/tests/regressiontests/model_forms_regress/models.py +++ b/tests/model_forms_regress/models.py diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/model_forms_regress/tests.py index 90c907f2a6..90c907f2a6 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/model_forms_regress/tests.py diff --git a/tests/regressiontests/fixtures_regress/__init__.py b/tests/model_formsets/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/fixtures_regress/__init__.py +++ b/tests/model_formsets/__init__.py diff --git a/tests/modeltests/model_formsets/models.py b/tests/model_formsets/models.py index ae152448ab..ae152448ab 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/model_formsets/models.py diff --git a/tests/modeltests/model_formsets/tests.py b/tests/model_formsets/tests.py index e28560b237..8d0c017a61 100644 --- a/tests/modeltests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -42,21 +42,29 @@ class DeletionTests(TestCase): doesn't cause validation errors. """ PoetFormSet = modelformset_factory(Poet, can_delete=True) + poet = Poet.objects.create(name='test') + # One existing untouched and two new unvalid forms data = { - 'form-TOTAL_FORMS': '1', - 'form-INITIAL_FORMS': '0', + 'form-TOTAL_FORMS': '3', + 'form-INITIAL_FORMS': '1', 'form-MAX_NUM_FORMS': '0', - 'form-0-id': '', - 'form-0-name': 'x' * 1000, + 'form-0-id': six.text_type(poet.id), + 'form-0-name': 'test', + 'form-1-id': '', + 'form-1-name': 'x' * 1000, # Too long + 'form-1-id': six.text_type(poet.id), # Violate unique constraint + 'form-1-name': 'test2', } formset = PoetFormSet(data, queryset=Poet.objects.all()) # Make sure this form doesn't pass validation. self.assertEqual(formset.is_valid(), False) - self.assertEqual(Poet.objects.count(), 0) + self.assertEqual(Poet.objects.count(), 1) # Then make sure that it *does* pass validation and delete the object, - # even though the data isn't actually valid. + # even though the data in new forms aren't actually valid. data['form-0-DELETE'] = 'on' + data['form-1-DELETE'] = 'on' + data['form-2-DELETE'] = 'on' formset = PoetFormSet(data, queryset=Poet.objects.all()) self.assertEqual(formset.is_valid(), True) formset.save() @@ -64,7 +72,7 @@ class DeletionTests(TestCase): def test_change_form_deletion_when_invalid(self): """ - Make sure that an add form that is filled out, but marked for deletion + Make sure that a change form that is filled out, but marked for deletion doesn't cause validation errors. """ PoetFormSet = modelformset_factory(Poet, can_delete=True) @@ -384,7 +392,7 @@ class ModelFormsetTest(TestCase): self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>\n' - '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>') + '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="number" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>') data = { 'form-TOTAL_FORMS': '1', # the number of forms rendered @@ -407,10 +415,10 @@ class ModelFormsetTest(TestCase): self.assertEqual(len(formset.forms), 2) self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Ernest Hemingway" maxlength="100" /></p>\n' - '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="%d" id="id_form-0-author_ptr" /></p>' % hemingway_id) + '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="number" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="%d" id="id_form-0-author_ptr" /></p>' % hemingway_id) self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>\n' - '<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>') + '<p><label for="id_form-1-write_speed">Write speed:</label> <input type="number" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>') data = { 'form-TOTAL_FORMS': '2', # the number of forms rendered @@ -543,6 +551,7 @@ class ModelFormsetTest(TestCase): def test_inline_formsets_with_custom_pk(self): # Test inline formsets where the inline-edited object has a custom # primary key that is not the fk to the parent object. + self.maxDiff = 1024 AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1) author = Author.objects.create(pk=1, name='Charles Baudelaire') @@ -550,7 +559,7 @@ class ModelFormsetTest(TestCase): formset = AuthorBooksFormSet2(instance=author) self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), - '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input type="text" name="bookwithcustompk_set-0-my_pk" id="id_bookwithcustompk_set-0-my_pk" /></p>\n' + '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" maxlength="6" /></p>\n' '<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>') data = { @@ -798,7 +807,7 @@ class ModelFormsetTest(TestCase): '<option value="%d">Joe Perry at Giordanos</option>\n' '<option value="%d">Jack Berry at Giordanos</option>\n' '</select></p>\n' - '<p><label for="id_form-0-age">Age:</label> <input type="text" name="form-0-age" id="id_form-0-age" /></p>' + '<p><label for="id_form-0-age">Age:</label> <input type="number" name="form-0-age" id="id_form-0-age" min="0" /></p>' % (owner1.auto_id, owner2.auto_id)) owner1 = Owner.objects.get(name='Joe Perry') @@ -808,7 +817,7 @@ class ModelFormsetTest(TestCase): formset = FormSet(instance=owner1) self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), - '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>' + '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="number" name="ownerprofile-0-age" id="id_ownerprofile-0-age" min="0" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>' % owner1.auto_id) data = { @@ -829,7 +838,7 @@ class ModelFormsetTest(TestCase): formset = FormSet(instance=owner1) self.assertEqual(len(formset.forms), 1) self.assertHTMLEqual(formset.forms[0].as_p(), - '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>' + '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="number" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" min="0" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>' % owner1.auto_id) data = { @@ -890,6 +899,33 @@ class ModelFormsetTest(TestCase): self.assertFalse(formset.is_valid()) self.assertEqual(formset.errors, [{'slug': ['Product with this Slug already exists.']}]) + def test_modelformset_validate_max_flag(self): + # If validate_max is set and max_num is less than TOTAL_FORMS in the + # data, then throw an exception. MAX_NUM_FORMS in the data is + # irrelevant here (it's output as a hint for the client but its + # value in the returned data is not checked) + + data = { + 'form-TOTAL_FORMS': '2', + 'form-INITIAL_FORMS': '0', + 'form-MAX_NUM_FORMS': '2', # should be ignored + 'form-0-price': '12.00', + 'form-0-quantity': '1', + 'form-1-price': '24.00', + 'form-1-quantity': '2', + } + + FormSet = modelformset_factory(Price, extra=1, max_num=1, validate_max=True) + formset = FormSet(data) + self.assertFalse(formset.is_valid()) + self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.']) + + # Now test the same thing without the validate_max flag to ensure + # default behavior is unchanged + FormSet = modelformset_factory(Price, extra=1, max_num=1) + formset = FormSet(data) + self.assertTrue(formset.is_valid()) + def test_unique_together_validation(self): FormSet = modelformset_factory(Price, extra=1) data = { @@ -985,7 +1021,7 @@ class ModelFormsetTest(TestCase): result = re.sub(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?', '__DATETIME__', result) self.assertHTMLEqual(result, '<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="__DATETIME__" id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="__DATETIME__" id="initial-membership_set-0-id_membership_set-0-date_joined" /></p>\n' - '<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="%d" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>' + '<p><label for="id_membership_set-0-karma">Karma:</label> <input type="number" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="%d" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>' % person.id) # test for validation with callable defaults. Validations rely on hidden fields @@ -1062,6 +1098,38 @@ class ModelFormsetTest(TestCase): FormSet = modelformset_factory(ClassyMexicanRestaurant, fields=["tacos_are_yummy"]) self.assertEqual(sorted(FormSet().forms[0].fields.keys()), ['restaurant', 'tacos_are_yummy']) + def test_model_formset_with_initial_model_instance(self): + # has_changed should compare model instance and primary key + # see #18898 + FormSet = modelformset_factory(Poem) + john_milton = Poet(name="John Milton") + john_milton.save() + data = { + 'form-TOTAL_FORMS': 1, + 'form-INITIAL_FORMS': 0, + 'form-MAX_NUM_FORMS': '', + 'form-0-name': '', + 'form-0-poet': str(john_milton.id), + } + formset = FormSet(initial=[{'poet': john_milton}], data=data) + self.assertFalse(formset.extra_forms[0].has_changed()) + + def test_model_formset_with_initial_queryset(self): + # has_changed should work with queryset and list of pk's + # see #18898 + FormSet = modelformset_factory(AuthorMeeting) + author = Author.objects.create(pk=1, name='Charles Baudelaire') + data = { + 'form-TOTAL_FORMS': 1, + 'form-INITIAL_FORMS': 0, + 'form-MAX_NUM_FORMS': '', + 'form-0-name': '', + 'form-0-created': '', + 'form-0-authors': list(Author.objects.values_list('id', flat=True)), + } + formset = FormSet(initial=[{'authors': Author.objects.all()}], data=data) + self.assertFalse(formset.extra_forms[0].has_changed()) + def test_prevent_duplicates_from_with_the_same_formset(self): FormSet = modelformset_factory(Product, extra=2) data = { @@ -1190,3 +1258,27 @@ class ModelFormsetTest(TestCase): self.assertFalse(formset.is_valid()) self.assertEqual(formset._non_form_errors, ['Please correct the duplicate data for subtitle which must be unique for the month in posted.']) + + +class TestModelFormsetWidgets(TestCase): + def test_modelformset_factory_widgets(self): + widgets = { + 'name': forms.TextInput(attrs={'class': 'poet'}) + } + PoetFormSet = modelformset_factory(Poet, widgets=widgets) + form = PoetFormSet.form() + self.assertHTMLEqual( + "%s" % form['name'], + '<input id="id_name" maxlength="100" type="text" class="poet" name="name" />' + ) + + def test_inlineformset_factory_widgets(self): + widgets = { + 'title': forms.TextInput(attrs={'class': 'book'}) + } + BookFormSet = inlineformset_factory(Author, Book, widgets=widgets) + form = BookFormSet.form() + self.assertHTMLEqual( + "%s" % form['title'], + '<input class="book" id="id_title" maxlength="100" name="title" type="text" />' + ) diff --git a/tests/regressiontests/forms/__init__.py b/tests/model_formsets_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/forms/__init__.py +++ b/tests/model_formsets_regress/__init__.py diff --git a/tests/regressiontests/model_formsets_regress/models.py b/tests/model_formsets_regress/models.py index f94ad51929..f94ad51929 100644 --- a/tests/regressiontests/model_formsets_regress/models.py +++ b/tests/model_formsets_regress/models.py diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/model_formsets_regress/tests.py index 8cadcfc409..fd35eda854 100644 --- a/tests/regressiontests/model_formsets_regress/tests.py +++ b/tests/model_formsets_regress/tests.py @@ -261,14 +261,17 @@ class FormsetTests(TestCase): formset.save() -class CustomWidget(forms.CharField): +class CustomWidget(forms.widgets.TextInput): pass class UserSiteForm(forms.ModelForm): class Meta: model = UserSite - widgets = {'data': CustomWidget} + widgets = { + 'id': CustomWidget, + 'data': CustomWidget, + } class Callback(object): @@ -283,24 +286,27 @@ class Callback(object): class FormfieldCallbackTests(TestCase): """ - Regression for #13095: Using base forms with widgets - defined in Meta should not raise errors. + Regression for #13095 and #17683: Using base forms with widgets + defined in Meta should not raise errors and BaseModelForm should respect + the specified pk widget. """ def test_inlineformset_factory_default(self): Formset = inlineformset_factory(User, UserSite, form=UserSiteForm) form = Formset().forms[0] + self.assertTrue(isinstance(form['id'].field.widget, CustomWidget)) 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['id'].field.widget, CustomWidget)) 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, {}), + (id_field, {'widget': CustomWidget}), (user_field, {}), (data_field, {'widget': CustomWidget}), ] @@ -318,7 +324,6 @@ class FormfieldCallbackTests(TestCase): formfield_callback=callback) self.assertCallbackCalled(callback) - class BaseCustomDeleteFormSet(BaseFormSet): """ A formset mix-in that lets a form decide if it's to be deleted. diff --git a/tests/regressiontests/generic_inline_admin/__init__.py b/tests/model_inheritance/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/generic_inline_admin/__init__.py +++ b/tests/model_inheritance/__init__.py diff --git a/tests/modeltests/model_inheritance/models.py b/tests/model_inheritance/models.py index 2101f394f7..2101f394f7 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index 16d2242fbe..dc40d2d2e0 100644 --- a/tests/modeltests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -1,9 +1,11 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from operator import attrgetter from django.core.exceptions import FieldError +from django.db import connection from django.test import TestCase +from django.test.utils import CaptureQueriesContext from django.utils import six from .models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, @@ -292,5 +294,27 @@ class ModelInheritanceTests(TestCase): rating=4, chef=c ) - with self.assertNumQueries(6): + with self.assertNumQueries(3): ir.save() + + def test_update_parent_filtering(self): + """ + Test that updating a field of a model subclass doesn't issue an UPDATE + query constrained by an inner query. + Refs #10399 + """ + supplier = Supplier.objects.create( + name='Central market', + address='610 some street' + ) + # Capture the expected query in a database agnostic way + with CaptureQueriesContext(connection) as captured_queries: + Place.objects.filter(pk=supplier.pk).update(name=supplier.name) + expected_sql = captured_queries[0]['sql'] + # Capture the queries executed when a subclassed model instance is saved. + with CaptureQueriesContext(connection) as captured_queries: + supplier.save(update_fields=('name',)) + for query in captured_queries: + sql = query['sql'] + if 'UPDATE' in sql: + self.assertEqual(expected_sql, sql) diff --git a/tests/regressiontests/generic_relations_regress/__init__.py b/tests/model_inheritance_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/generic_relations_regress/__init__.py +++ b/tests/model_inheritance_regress/__init__.py diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py index 811c8175bb..811c8175bb 100644 --- a/tests/regressiontests/model_inheritance_regress/models.py +++ b/tests/model_inheritance_regress/models.py diff --git a/tests/regressiontests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py index 6855d70071..c2c9337485 100644 --- a/tests/regressiontests/model_inheritance_regress/tests.py +++ b/tests/model_inheritance_regress/tests.py @@ -8,6 +8,7 @@ from operator import attrgetter from django import forms from django.test import TestCase +from django.utils.unittest import expectedFailure from .models import (Place, Restaurant, ItalianRestaurant, ParkingLot, ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent, @@ -134,8 +135,8 @@ class ModelInheritanceTest(TestCase): 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)]) + datetimes = list(Child.objects.datetimes('created', 'month')) + self.assertEqual(datetimes, [datetime.datetime(2008, 6, 1, 0, 0)]) def test_issue_7276(self): # Regression test for #7276: calling delete() on a model with @@ -180,11 +181,11 @@ class ModelInheritanceTest(TestCase): """ Regression test for #6755 """ - r = Restaurant(serves_pizza=False) + r = Restaurant(serves_pizza=False, serves_hot_dogs=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 = Restaurant(place_ptr_id=orig_id, serves_pizza=True, serves_hot_dogs=False) r.save() self.assertEqual(r.id, orig_id) self.assertEqual(r.id, r.place_ptr_id) @@ -422,3 +423,19 @@ class ModelInheritanceTest(TestCase): form = ProfileForm({'username': "user_with_profile", 'extra': "hello"}, instance=p) self.assertTrue(form.is_valid()) + + def test_inheritance_joins(self): + # Test for #17502 - check that filtering through two levels of + # inheritance chain doesn't generate extra joins. + qs = ItalianRestaurant.objects.all() + self.assertEqual(str(qs.query).count('JOIN'), 2) + qs = ItalianRestaurant.objects.filter(name='foo') + self.assertEqual(str(qs.query).count('JOIN'), 2) + + @expectedFailure + def test_inheritance_values_joins(self): + # It would be nice (but not too important) to skip the middle join in + # this case. Skipping is possible as nothing from the middle model is + # used in the qs and top contains direct pointer to the bottom model. + qs = ItalianRestaurant.objects.values_list('serves_gnocchi').filter(name='foo') + self.assertEqual(str(qs.query).count('JOIN'), 1) diff --git a/tests/regressiontests/generic_views/__init__.py b/tests/model_inheritance_same_model_name/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/generic_views/__init__.py +++ b/tests/model_inheritance_same_model_name/__init__.py diff --git a/tests/modeltests/model_inheritance_same_model_name/models.py b/tests/model_inheritance_same_model_name/models.py index 801724df18..b4a6b930df 100644 --- a/tests/modeltests/model_inheritance_same_model_name/models.py +++ b/tests/model_inheritance_same_model_name/models.py @@ -10,7 +10,7 @@ from __future__ import absolute_import from django.db import models -from ..model_inheritance.models import NamedURL +from model_inheritance.models import NamedURL from django.utils.encoding import python_2_unicode_compatible # diff --git a/tests/modeltests/model_inheritance_same_model_name/tests.py b/tests/model_inheritance_same_model_name/tests.py index 13d51f1a67..8f22578013 100644 --- a/tests/modeltests/model_inheritance_same_model_name/tests.py +++ b/tests/model_inheritance_same_model_name/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from django.test import TestCase -from ..model_inheritance.models import Title +from model_inheritance.models import Title class InheritanceSameModelNameTests(TestCase): @@ -14,7 +14,7 @@ class InheritanceSameModelNameTests(TestCase): self.title = Title.objects.create(title='Lorem Ipsum') def test_inheritance_related_name(self): - from modeltests.model_inheritance.models import Copy + from model_inheritance.models import Copy self.assertEqual( self.title.attached_model_inheritance_copy_set.create( content='Save $ on V1agr@', @@ -23,7 +23,7 @@ class InheritanceSameModelNameTests(TestCase): ), Copy.objects.get(content='Save $ on V1agr@')) def test_inheritance_with_same_model_name(self): - from modeltests.model_inheritance_same_model_name.models import Copy + from model_inheritance_same_model_name.models import Copy self.assertEqual( self.title.attached_model_inheritance_same_model_name_copy_set.create( content='The Web framework for perfectionists with deadlines.', diff --git a/tests/regressiontests/get_or_create_regress/__init__.py b/tests/model_inheritance_select_related/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/get_or_create_regress/__init__.py +++ b/tests/model_inheritance_select_related/__init__.py diff --git a/tests/regressiontests/model_inheritance_select_related/models.py b/tests/model_inheritance_select_related/models.py index 6b28772620..6b28772620 100644 --- a/tests/regressiontests/model_inheritance_select_related/models.py +++ b/tests/model_inheritance_select_related/models.py diff --git a/tests/regressiontests/model_inheritance_select_related/tests.py b/tests/model_inheritance_select_related/tests.py index 078b466d0e..078b466d0e 100644 --- a/tests/regressiontests/model_inheritance_select_related/tests.py +++ b/tests/model_inheritance_select_related/tests.py diff --git a/tests/regressiontests/max_lengths/__init__.py b/tests/model_package/__init__.py index 8b13789179..8b13789179 100644 --- a/tests/regressiontests/max_lengths/__init__.py +++ b/tests/model_package/__init__.py diff --git a/tests/modeltests/model_package/models/__init__.py b/tests/model_package/models/__init__.py index 3c261aa444..3c261aa444 100644 --- a/tests/modeltests/model_package/models/__init__.py +++ b/tests/model_package/models/__init__.py diff --git a/tests/modeltests/model_package/models/article.py b/tests/model_package/models/article.py index f70c971c7e..f70c971c7e 100644 --- a/tests/modeltests/model_package/models/article.py +++ b/tests/model_package/models/article.py diff --git a/tests/modeltests/model_package/models/publication.py b/tests/model_package/models/publication.py index 29828b9295..29828b9295 100644 --- a/tests/modeltests/model_package/models/publication.py +++ b/tests/model_package/models/publication.py diff --git a/tests/modeltests/model_package/tests.py b/tests/model_package/tests.py index 5cf464858b..5cf464858b 100644 --- a/tests/modeltests/model_package/tests.py +++ b/tests/model_package/tests.py diff --git a/tests/regressiontests/handlers/__init__.py b/tests/model_permalink/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/handlers/__init__.py +++ b/tests/model_permalink/__init__.py diff --git a/tests/regressiontests/model_permalink/models.py b/tests/model_permalink/models.py index dacf2a3fb3..dacf2a3fb3 100644 --- a/tests/regressiontests/model_permalink/models.py +++ b/tests/model_permalink/models.py diff --git a/tests/regressiontests/model_permalink/tests.py b/tests/model_permalink/tests.py index 049f338c2e..257648ca5f 100644 --- a/tests/regressiontests/model_permalink/tests.py +++ b/tests/model_permalink/tests.py @@ -6,7 +6,7 @@ from .models import Guitarist class PermalinkTests(TestCase): - urls = 'regressiontests.model_permalink.urls' + urls = 'model_permalink.urls' def test_permalink(self): g = Guitarist(name='Adrien Moignard', slug='adrienmoignard') diff --git a/tests/regressiontests/model_permalink/urls.py b/tests/model_permalink/urls.py index 409fc222c8..409fc222c8 100644 --- a/tests/regressiontests/model_permalink/urls.py +++ b/tests/model_permalink/urls.py diff --git a/tests/regressiontests/http_utils/__init__.py b/tests/model_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/http_utils/__init__.py +++ b/tests/model_regress/__init__.py diff --git a/tests/regressiontests/model_regress/models.py b/tests/model_regress/models.py index 82efb9c783..82efb9c783 100644 --- a/tests/regressiontests/model_regress/models.py +++ b/tests/model_regress/models.py diff --git a/tests/regressiontests/model_regress/tests.py b/tests/model_regress/tests.py index c90fe98658..c1e864c270 100644 --- a/tests/regressiontests/model_regress/tests.py +++ b/tests/model_regress/tests.py @@ -7,6 +7,8 @@ from django.core.exceptions import ValidationError from django.test import TestCase, skipUnlessDBFeature from django.utils import six from django.utils import tzinfo +from django.db import router +from django.db.models.sql import InsertQuery from .models import (Worker, Article, Party, Event, Department, BrokenUnicodeMethod, NonAutoPK, Model1, Model2, Model3) @@ -27,6 +29,16 @@ class ModelTests(TestCase): """ Worker.objects.filter(department__lte=0) + def test_sql_insert_compiler_return_id_attribute(self): + """ + Regression test for #14019: SQLInsertCompiler.as_sql() failure + """ + db = router.db_for_write(Party) + query = InsertQuery(Party) + query.insert_values([Party._meta.fields[0]], [], raw=False) + # this line will raise an AttributeError without the accompanying fix + query.get_compiler(using=db).as_sql() + 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. @@ -57,6 +69,7 @@ class ModelTests(TestCase): 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)) + Party.objects.create(when=datetime.datetime(1, 3, 3)) self.assertQuerysetEqual( Party.objects.filter(when__month=2), [] ) @@ -104,6 +117,20 @@ class ModelTests(TestCase): attrgetter("when") ) + # Regression test for #18969 + self.assertQuerysetEqual( + Party.objects.filter(when__year=1), [ + datetime.date(1, 3, 3), + ], + attrgetter("when") + ) + self.assertQuerysetEqual( + Party.objects.filter(when__year='1'), [ + datetime.date(1, 3, 3), + ], + 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). diff --git a/tests/regressiontests/httpwrappers/__init__.py b/tests/modeladmin/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/httpwrappers/__init__.py +++ b/tests/modeladmin/__init__.py diff --git a/tests/regressiontests/modeladmin/models.py b/tests/modeladmin/models.py index fdbcabd187..fdbcabd187 100644 --- a/tests/regressiontests/modeladmin/models.py +++ b/tests/modeladmin/models.py diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/modeladmin/tests.py index b0a181218b..e5450ab8ff 100644 --- a/tests/regressiontests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -69,7 +69,7 @@ class ModelAdminTests(TestCase): # 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'] + fields = ['name'] ma = BandAdmin(Band, self.site) @@ -1074,7 +1074,7 @@ class ValidationTests(unittest.TestCase): return 'awesomeness' def get_choices(self, request): return (('bit', 'A bit awesome'), ('very', 'Very awesome'), ) - def get_query_set(self, cl, qs): + def get_queryset(self, cl, qs): return qs class ValidationTestModelAdmin(ModelAdmin): diff --git a/tests/modeltests/empty/no_models/tests.py b/tests/modeltests/empty/no_models/tests.py deleted file mode 100644 index 6292ca9f74..0000000000 --- a/tests/modeltests/empty/no_models/tests.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.test import TestCase - - -class NoModelTests(TestCase): - """ A placeholder test case. See modeltests.empty.tests for more info. """ - pass diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py deleted file mode 100644 index fe594dd802..0000000000 --- a/tests/modeltests/get_latest/models.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -8. get_latest_by - -Models can have a ``get_latest_by`` attribute, which should be set to the name -of a ``DateField`` or ``DateTimeField``. If ``get_latest_by`` exists, the -model's manager will get a ``latest()`` method, which will return the latest -object in the database according to that field. "Latest" means "having the date -farthest into the future." -""" - -from django.db import models -from django.utils.encoding import python_2_unicode_compatible - - -@python_2_unicode_compatible -class Article(models.Model): - headline = models.CharField(max_length=100) - pub_date = models.DateField() - expire_date = models.DateField() - class Meta: - get_latest_by = 'pub_date' - - def __str__(self): - return self.headline - -@python_2_unicode_compatible -class Person(models.Model): - name = models.CharField(max_length=30) - birthday = models.DateField() - - # Note that this model doesn't have "get_latest_by" set. - - def __str__(self): - return self.name diff --git a/tests/modeltests/get_latest/tests.py b/tests/modeltests/get_latest/tests.py deleted file mode 100644 index 948af6045a..0000000000 --- a/tests/modeltests/get_latest/tests.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import absolute_import - -from datetime import datetime - -from django.test import TestCase - -from .models import Article, Person - - -class LatestTests(TestCase): - def test_latest(self): - # Because no Articles exist yet, latest() raises ArticleDoesNotExist. - self.assertRaises(Article.DoesNotExist, Article.objects.latest) - - a1 = Article.objects.create( - headline="Article 1", pub_date=datetime(2005, 7, 26), - expire_date=datetime(2005, 9, 1) - ) - a2 = Article.objects.create( - headline="Article 2", pub_date=datetime(2005, 7, 27), - expire_date=datetime(2005, 7, 28) - ) - a3 = Article.objects.create( - headline="Article 3", pub_date=datetime(2005, 7, 27), - expire_date=datetime(2005, 8, 27) - ) - a4 = Article.objects.create( - headline="Article 4", pub_date=datetime(2005, 7, 28), - expire_date=datetime(2005, 7, 30) - ) - - # Get the latest Article. - self.assertEqual(Article.objects.latest(), a4) - # Get the latest Article that matches certain filters. - self.assertEqual( - Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(), - a1 - ) - - # Pass a custom field name to latest() to change the field that's used - # to determine the latest object. - self.assertEqual(Article.objects.latest('expire_date'), a1) - self.assertEqual( - Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'), - a3, - ) - - # Ensure that latest() overrides any other ordering specified on the query. Refs #11283. - self.assertEqual(Article.objects.order_by('id').latest(), a4) - - def test_latest_manual(self): - # You can still use latest() with a model that doesn't have - # "get_latest_by" set -- just pass in the field name manually. - p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1)) - p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3)) - self.assertRaises(AssertionError, Person.objects.latest) - - self.assertEqual(Person.objects.latest("birthday"), p2) diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py deleted file mode 100644 index 98358e3d10..0000000000 --- a/tests/modeltests/lookup/tests.py +++ /dev/null @@ -1,688 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -from datetime import datetime -from operator import attrgetter - -from django.core.exceptions import FieldError -from django.test import TestCase, skipUnlessDBFeature - -from .models import Author, Article, Tag, Game, Season, Player - - -class LookupTests(TestCase): - - def setUp(self): - # Create a few Authors. - self.au1 = Author(name='Author 1') - self.au1.save() - self.au2 = Author(name='Author 2') - self.au2.save() - # Create a couple of Articles. - self.a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26), author=self.au1) - self.a1.save() - self.a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27), author=self.au1) - self.a2.save() - self.a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27), author=self.au1) - self.a3.save() - self.a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28), author=self.au1) - self.a4.save() - self.a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0), author=self.au2) - self.a5.save() - self.a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0), author=self.au2) - self.a6.save() - self.a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27), author=self.au2) - self.a7.save() - # Create a few Tags. - self.t1 = Tag(name='Tag 1') - self.t1.save() - self.t1.articles.add(self.a1, self.a2, self.a3) - self.t2 = Tag(name='Tag 2') - self.t2.save() - self.t2.articles.add(self.a3, self.a4, self.a5) - self.t3 = Tag(name='Tag 3') - self.t3.save() - self.t3.articles.add(self.a5, self.a6, self.a7) - - def test_exists(self): - # We can use .exists() to check that there are some - self.assertTrue(Article.objects.exists()) - for a in Article.objects.all(): - a.delete() - # There should be none now! - self.assertFalse(Article.objects.exists()) - - def test_lookup_int_as_str(self): - # Integer value can be queried using string - self.assertQuerysetEqual(Article.objects.filter(id__iexact=str(self.a1.id)), - ['<Article: Article 1>']) - - @skipUnlessDBFeature('supports_date_lookup_using_string') - def test_lookup_date_as_str(self): - # A date lookup can be performed using a string search - self.assertQuerysetEqual(Article.objects.filter(pub_date__startswith='2005'), - [ - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 7>', - '<Article: Article 1>', - ]) - - def test_iterator(self): - # Each QuerySet gets iterator(), which is a generator that "lazily" - # returns results using database-level iteration. - self.assertQuerysetEqual(Article.objects.iterator(), - [ - 'Article 5', - 'Article 6', - 'Article 4', - 'Article 2', - 'Article 3', - 'Article 7', - 'Article 1', - ], - transform=attrgetter('headline')) - # iterator() can be used on any QuerySet. - self.assertQuerysetEqual( - Article.objects.filter(headline__endswith='4').iterator(), - ['Article 4'], - transform=attrgetter('headline')) - - def test_count(self): - # count() returns the number of objects matching search criteria. - self.assertEqual(Article.objects.count(), 7) - self.assertEqual(Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).count(), 3) - self.assertEqual(Article.objects.filter(headline__startswith='Blah blah').count(), 0) - - # count() should respect sliced query sets. - articles = Article.objects.all() - self.assertEqual(articles.count(), 7) - self.assertEqual(articles[:4].count(), 4) - self.assertEqual(articles[1:100].count(), 6) - self.assertEqual(articles[10:100].count(), 0) - - # Date and date/time lookups can also be done with strings. - self.assertEqual(Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count(), 3) - - def test_in_bulk(self): - # in_bulk() takes a list of IDs and returns a dictionary mapping IDs to objects. - arts = Article.objects.in_bulk([self.a1.id, self.a2.id]) - self.assertEqual(arts[self.a1.id], self.a1) - self.assertEqual(arts[self.a2.id], self.a2) - self.assertEqual(Article.objects.in_bulk([self.a3.id]), {self.a3.id: self.a3}) - self.assertEqual(Article.objects.in_bulk(set([self.a3.id])), {self.a3.id: self.a3}) - self.assertEqual(Article.objects.in_bulk(frozenset([self.a3.id])), {self.a3.id: self.a3}) - self.assertEqual(Article.objects.in_bulk((self.a3.id,)), {self.a3.id: self.a3}) - self.assertEqual(Article.objects.in_bulk([1000]), {}) - self.assertEqual(Article.objects.in_bulk([]), {}) - self.assertEqual(Article.objects.in_bulk(iter([self.a1.id])), {self.a1.id: self.a1}) - self.assertEqual(Article.objects.in_bulk(iter([])), {}) - self.assertRaises(TypeError, Article.objects.in_bulk) - self.assertRaises(TypeError, Article.objects.in_bulk, headline__startswith='Blah') - - def test_values(self): - # values() returns a list of dictionaries instead of object instances -- - # and you can specify which fields you want to retrieve. - identity = lambda x:x - self.assertQuerysetEqual(Article.objects.values('headline'), - [ - {'headline': 'Article 5'}, - {'headline': 'Article 6'}, - {'headline': 'Article 4'}, - {'headline': 'Article 2'}, - {'headline': 'Article 3'}, - {'headline': 'Article 7'}, - {'headline': 'Article 1'}, - ], - transform=identity) - self.assertQuerysetEqual( - Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id'), - [{'id': self.a2.id}, {'id': self.a3.id}, {'id': self.a7.id}], - transform=identity) - self.assertQuerysetEqual(Article.objects.values('id', 'headline'), - [ - {'id': self.a5.id, 'headline': 'Article 5'}, - {'id': self.a6.id, 'headline': 'Article 6'}, - {'id': self.a4.id, 'headline': 'Article 4'}, - {'id': self.a2.id, 'headline': 'Article 2'}, - {'id': self.a3.id, 'headline': 'Article 3'}, - {'id': self.a7.id, 'headline': 'Article 7'}, - {'id': self.a1.id, 'headline': 'Article 1'}, - ], - transform=identity) - # You can use values() with iterator() for memory savings, - # because iterator() uses database-level iteration. - self.assertQuerysetEqual(Article.objects.values('id', 'headline').iterator(), - [ - {'headline': 'Article 5', 'id': self.a5.id}, - {'headline': 'Article 6', 'id': self.a6.id}, - {'headline': 'Article 4', 'id': self.a4.id}, - {'headline': 'Article 2', 'id': self.a2.id}, - {'headline': 'Article 3', 'id': self.a3.id}, - {'headline': 'Article 7', 'id': self.a7.id}, - {'headline': 'Article 1', 'id': self.a1.id}, - ], - transform=identity) - # The values() method works with "extra" fields specified in extra(select). - self.assertQuerysetEqual( - Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_one'), - [ - {'id': self.a5.id, 'id_plus_one': self.a5.id + 1}, - {'id': self.a6.id, 'id_plus_one': self.a6.id + 1}, - {'id': self.a4.id, 'id_plus_one': self.a4.id + 1}, - {'id': self.a2.id, 'id_plus_one': self.a2.id + 1}, - {'id': self.a3.id, 'id_plus_one': self.a3.id + 1}, - {'id': self.a7.id, 'id_plus_one': self.a7.id + 1}, - {'id': self.a1.id, 'id_plus_one': self.a1.id + 1}, - ], - transform=identity) - data = { - 'id_plus_one': 'id+1', - 'id_plus_two': 'id+2', - 'id_plus_three': 'id+3', - 'id_plus_four': 'id+4', - 'id_plus_five': 'id+5', - 'id_plus_six': 'id+6', - 'id_plus_seven': 'id+7', - 'id_plus_eight': 'id+8', - } - self.assertQuerysetEqual( - Article.objects.filter(id=self.a1.id).extra(select=data).values(*data.keys()), - [{ - 'id_plus_one': self.a1.id + 1, - 'id_plus_two': self.a1.id + 2, - 'id_plus_three': self.a1.id + 3, - 'id_plus_four': self.a1.id + 4, - 'id_plus_five': self.a1.id + 5, - 'id_plus_six': self.a1.id + 6, - 'id_plus_seven': self.a1.id + 7, - 'id_plus_eight': self.a1.id + 8, - }], transform=identity) - # You can specify fields from forward and reverse relations, just like filter(). - self.assertQuerysetEqual( - Article.objects.values('headline', 'author__name'), - [ - {'headline': self.a5.headline, 'author__name': self.au2.name}, - {'headline': self.a6.headline, 'author__name': self.au2.name}, - {'headline': self.a4.headline, 'author__name': self.au1.name}, - {'headline': self.a2.headline, 'author__name': self.au1.name}, - {'headline': self.a3.headline, 'author__name': self.au1.name}, - {'headline': self.a7.headline, 'author__name': self.au2.name}, - {'headline': self.a1.headline, 'author__name': self.au1.name}, - ], transform=identity) - self.assertQuerysetEqual( - Author.objects.values('name', 'article__headline').order_by('name', 'article__headline'), - [ - {'name': self.au1.name, 'article__headline': self.a1.headline}, - {'name': self.au1.name, 'article__headline': self.a2.headline}, - {'name': self.au1.name, 'article__headline': self.a3.headline}, - {'name': self.au1.name, 'article__headline': self.a4.headline}, - {'name': self.au2.name, 'article__headline': self.a5.headline}, - {'name': self.au2.name, 'article__headline': self.a6.headline}, - {'name': self.au2.name, 'article__headline': self.a7.headline}, - ], transform=identity) - self.assertQuerysetEqual( - Author.objects.values('name', 'article__headline', 'article__tag__name').order_by('name', 'article__headline', 'article__tag__name'), - [ - {'name': self.au1.name, 'article__headline': self.a1.headline, 'article__tag__name': self.t1.name}, - {'name': self.au1.name, 'article__headline': self.a2.headline, 'article__tag__name': self.t1.name}, - {'name': self.au1.name, 'article__headline': self.a3.headline, 'article__tag__name': self.t1.name}, - {'name': self.au1.name, 'article__headline': self.a3.headline, 'article__tag__name': self.t2.name}, - {'name': self.au1.name, 'article__headline': self.a4.headline, 'article__tag__name': self.t2.name}, - {'name': self.au2.name, 'article__headline': self.a5.headline, 'article__tag__name': self.t2.name}, - {'name': self.au2.name, 'article__headline': self.a5.headline, 'article__tag__name': self.t3.name}, - {'name': self.au2.name, 'article__headline': self.a6.headline, 'article__tag__name': self.t3.name}, - {'name': self.au2.name, 'article__headline': self.a7.headline, 'article__tag__name': self.t3.name}, - ], transform=identity) - # However, an exception FieldDoesNotExist will be thrown if you specify - # a non-existent field name in values() (a field that is neither in the - # model nor in extra(select)). - self.assertRaises(FieldError, - Article.objects.extra(select={'id_plus_one': 'id + 1'}).values, - 'id', 'id_plus_two') - # If you don't specify field names to values(), all are returned. - self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(), - [{ - 'id': self.a5.id, - 'author_id': self.au2.id, - 'headline': 'Article 5', - 'pub_date': datetime(2005, 8, 1, 9, 0) - }], transform=identity) - - def test_values_list(self): - # values_list() is similar to values(), except that the results are - # returned as a list of tuples, rather than a list of dictionaries. - # Within each tuple, the order of the elements is the same as the order - # of fields in the values_list() call. - identity = lambda x:x - self.assertQuerysetEqual(Article.objects.values_list('headline'), - [ - ('Article 5',), - ('Article 6',), - ('Article 4',), - ('Article 2',), - ('Article 3',), - ('Article 7',), - ('Article 1',), - ], transform=identity) - self.assertQuerysetEqual(Article.objects.values_list('id').order_by('id'), - [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], - transform=identity) - self.assertQuerysetEqual( - Article.objects.values_list('id', flat=True).order_by('id'), - [self.a1.id, self.a2.id, self.a3.id, self.a4.id, self.a5.id, self.a6.id, self.a7.id], - transform=identity) - self.assertQuerysetEqual( - Article.objects.extra(select={'id_plus_one': 'id+1'}) - .order_by('id').values_list('id'), - [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], - transform=identity) - self.assertQuerysetEqual( - Article.objects.extra(select={'id_plus_one': 'id+1'}) - .order_by('id').values_list('id_plus_one', 'id'), - [ - (self.a1.id+1, self.a1.id), - (self.a2.id+1, self.a2.id), - (self.a3.id+1, self.a3.id), - (self.a4.id+1, self.a4.id), - (self.a5.id+1, self.a5.id), - (self.a6.id+1, self.a6.id), - (self.a7.id+1, self.a7.id) - ], - transform=identity) - self.assertQuerysetEqual( - Article.objects.extra(select={'id_plus_one': 'id+1'}) - .order_by('id').values_list('id', 'id_plus_one'), - [ - (self.a1.id, self.a1.id+1), - (self.a2.id, self.a2.id+1), - (self.a3.id, self.a3.id+1), - (self.a4.id, self.a4.id+1), - (self.a5.id, self.a5.id+1), - (self.a6.id, self.a6.id+1), - (self.a7.id, self.a7.id+1) - ], - transform=identity) - self.assertQuerysetEqual( - Author.objects.values_list('name', 'article__headline', 'article__tag__name').order_by('name', 'article__headline', 'article__tag__name'), - [ - (self.au1.name, self.a1.headline, self.t1.name), - (self.au1.name, self.a2.headline, self.t1.name), - (self.au1.name, self.a3.headline, self.t1.name), - (self.au1.name, self.a3.headline, self.t2.name), - (self.au1.name, self.a4.headline, self.t2.name), - (self.au2.name, self.a5.headline, self.t2.name), - (self.au2.name, self.a5.headline, self.t3.name), - (self.au2.name, self.a6.headline, self.t3.name), - (self.au2.name, self.a7.headline, self.t3.name), - ], transform=identity) - self.assertRaises(TypeError, Article.objects.values_list, 'id', 'headline', flat=True) - - def test_get_next_previous_by(self): - # Every DateField and DateTimeField creates get_next_by_FOO() and - # get_previous_by_FOO() methods. In the case of identical date values, - # these methods will use the ID as a fallback check. This guarantees - # that no records are skipped or duplicated. - self.assertEqual(repr(self.a1.get_next_by_pub_date()), - '<Article: Article 2>') - self.assertEqual(repr(self.a2.get_next_by_pub_date()), - '<Article: Article 3>') - self.assertEqual(repr(self.a2.get_next_by_pub_date(headline__endswith='6')), - '<Article: Article 6>') - self.assertEqual(repr(self.a3.get_next_by_pub_date()), - '<Article: Article 7>') - self.assertEqual(repr(self.a4.get_next_by_pub_date()), - '<Article: Article 6>') - self.assertRaises(Article.DoesNotExist, self.a5.get_next_by_pub_date) - self.assertEqual(repr(self.a6.get_next_by_pub_date()), - '<Article: Article 5>') - self.assertEqual(repr(self.a7.get_next_by_pub_date()), - '<Article: Article 4>') - - self.assertEqual(repr(self.a7.get_previous_by_pub_date()), - '<Article: Article 3>') - self.assertEqual(repr(self.a6.get_previous_by_pub_date()), - '<Article: Article 4>') - self.assertEqual(repr(self.a5.get_previous_by_pub_date()), - '<Article: Article 6>') - self.assertEqual(repr(self.a4.get_previous_by_pub_date()), - '<Article: Article 7>') - self.assertEqual(repr(self.a3.get_previous_by_pub_date()), - '<Article: Article 2>') - self.assertEqual(repr(self.a2.get_previous_by_pub_date()), - '<Article: Article 1>') - - def test_escaping(self): - # Underscores, percent signs and backslashes have special meaning in the - # underlying SQL code, but Django handles the quoting of them automatically. - a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) - a8.save() - self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article'), - [ - '<Article: Article_ with underscore>', - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 7>', - '<Article: Article 1>', - ]) - self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article_'), - ['<Article: Article_ with underscore>']) - a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) - a9.save() - self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article'), - [ - '<Article: Article% with percent sign>', - '<Article: Article_ with underscore>', - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 7>', - '<Article: Article 1>', - ]) - self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article%'), - ['<Article: Article% with percent sign>']) - a10 = Article(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22)) - a10.save() - self.assertQuerysetEqual(Article.objects.filter(headline__contains='\\'), - ['<Article: Article with \ backslash>']) - - def test_exclude(self): - a8 = Article.objects.create(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) - a9 = Article.objects.create(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) - a10 = Article.objects.create(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22)) - - # exclude() is the opposite of filter() when doing lookups: - self.assertQuerysetEqual( - Article.objects.filter(headline__contains='Article').exclude(headline__contains='with'), - [ - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 7>', - '<Article: Article 1>', - ]) - self.assertQuerysetEqual(Article.objects.exclude(headline__startswith="Article_"), - [ - '<Article: Article with \\ backslash>', - '<Article: Article% with percent sign>', - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 7>', - '<Article: Article 1>', - ]) - self.assertQuerysetEqual(Article.objects.exclude(headline="Article 7"), - [ - '<Article: Article with \\ backslash>', - '<Article: Article% with percent sign>', - '<Article: Article_ with underscore>', - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 1>', - ]) - - def test_none(self): - # none() returns an EmptyQuerySet that behaves like any other QuerySet object - self.assertQuerysetEqual(Article.objects.none(), []) - self.assertQuerysetEqual( - Article.objects.none().filter(headline__startswith='Article'), []) - self.assertQuerysetEqual( - Article.objects.filter(headline__startswith='Article').none(), []) - self.assertEqual(Article.objects.none().count(), 0) - self.assertEqual( - Article.objects.none().update(headline="This should not take effect"), 0) - self.assertQuerysetEqual( - [article for article in Article.objects.none().iterator()], - []) - - def test_in(self): - # using __in with an empty list should return an empty query set - self.assertQuerysetEqual(Article.objects.filter(id__in=[]), []) - self.assertQuerysetEqual(Article.objects.exclude(id__in=[]), - [ - '<Article: Article 5>', - '<Article: Article 6>', - '<Article: Article 4>', - '<Article: Article 2>', - '<Article: Article 3>', - '<Article: Article 7>', - '<Article: Article 1>', - ]) - - def test_error_messages(self): - # Programming errors are pointed out with nice error messages - try: - Article.objects.filter(pub_date_year='2005').count() - self.fail('FieldError not raised') - except FieldError as ex: - self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' " - "into field. Choices are: author, headline, id, pub_date, tag") - try: - Article.objects.filter(headline__starts='Article') - self.fail('FieldError not raised') - except FieldError as ex: - self.assertEqual(str(ex), "Join on field 'headline' not permitted. " - "Did you misspell 'starts' for the lookup type?") - - def test_regex(self): - # Create some articles with a bit more interesting headlines for testing field lookups: - for a in Article.objects.all(): - a.delete() - now = datetime.now() - a1 = Article(pub_date=now, headline='f') - a1.save() - a2 = Article(pub_date=now, headline='fo') - a2.save() - a3 = Article(pub_date=now, headline='foo') - a3.save() - a4 = Article(pub_date=now, headline='fooo') - a4.save() - a5 = Article(pub_date=now, headline='hey-Foo') - a5.save() - a6 = Article(pub_date=now, headline='bar') - a6.save() - a7 = Article(pub_date=now, headline='AbBa') - a7.save() - a8 = Article(pub_date=now, headline='baz') - a8.save() - a9 = Article(pub_date=now, headline='baxZ') - a9.save() - # zero-or-more - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'fo*'), - ['<Article: f>', '<Article: fo>', '<Article: foo>', '<Article: fooo>']) - self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'fo*'), - [ - '<Article: f>', - '<Article: fo>', - '<Article: foo>', - '<Article: fooo>', - '<Article: hey-Foo>', - ]) - # one-or-more - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'fo+'), - ['<Article: fo>', '<Article: foo>', '<Article: fooo>']) - # wildcard - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'fooo?'), - ['<Article: foo>', '<Article: fooo>']) - # leading anchor - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'^b'), - ['<Article: bar>', '<Article: baxZ>', '<Article: baz>']) - self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'^a'), - ['<Article: AbBa>']) - # trailing anchor - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'z$'), - ['<Article: baz>']) - self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'z$'), - ['<Article: baxZ>', '<Article: baz>']) - # character sets - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'ba[rz]'), - ['<Article: bar>', '<Article: baz>']) - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'ba.[RxZ]'), - ['<Article: baxZ>']) - self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'ba[RxZ]'), - ['<Article: bar>', '<Article: baxZ>', '<Article: baz>']) - - # and more articles: - a10 = Article(pub_date=now, headline='foobar') - a10.save() - a11 = Article(pub_date=now, headline='foobaz') - a11.save() - a12 = Article(pub_date=now, headline='ooF') - a12.save() - a13 = Article(pub_date=now, headline='foobarbaz') - a13.save() - a14 = Article(pub_date=now, headline='zoocarfaz') - a14.save() - a15 = Article(pub_date=now, headline='barfoobaz') - a15.save() - a16 = Article(pub_date=now, headline='bazbaRFOO') - a16.save() - - # alternation - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'oo(f|b)'), - [ - '<Article: barfoobaz>', - '<Article: foobar>', - '<Article: foobarbaz>', - '<Article: foobaz>', - ]) - self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'oo(f|b)'), - [ - '<Article: barfoobaz>', - '<Article: foobar>', - '<Article: foobarbaz>', - '<Article: foobaz>', - '<Article: ooF>', - ]) - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'^foo(f|b)'), - ['<Article: foobar>', '<Article: foobarbaz>', '<Article: foobaz>']) - - # greedy matching - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b.*az'), - [ - '<Article: barfoobaz>', - '<Article: baz>', - '<Article: bazbaRFOO>', - '<Article: foobarbaz>', - '<Article: foobaz>', - ]) - self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'b.*ar'), - [ - '<Article: bar>', - '<Article: barfoobaz>', - '<Article: bazbaRFOO>', - '<Article: foobar>', - '<Article: foobarbaz>', - ]) - - @skipUnlessDBFeature('supports_regex_backreferencing') - def test_regex_backreferencing(self): - # grouping and backreferences - now = datetime.now() - a10 = Article(pub_date=now, headline='foobar') - a10.save() - a11 = Article(pub_date=now, headline='foobaz') - a11.save() - a12 = Article(pub_date=now, headline='ooF') - a12.save() - a13 = Article(pub_date=now, headline='foobarbaz') - a13.save() - a14 = Article(pub_date=now, headline='zoocarfaz') - a14.save() - a15 = Article(pub_date=now, headline='barfoobaz') - a15.save() - a16 = Article(pub_date=now, headline='bazbaRFOO') - a16.save() - self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b(.).*b\1'), - ['<Article: barfoobaz>', '<Article: bazbaRFOO>', '<Article: foobarbaz>']) - - def test_nonfield_lookups(self): - """ - Ensure that a lookup query containing non-fields raises the proper - exception. - """ - with self.assertRaises(FieldError): - Article.objects.filter(headline__blahblah=99) - with self.assertRaises(FieldError): - Article.objects.filter(headline__blahblah__exact=99) - with self.assertRaises(FieldError): - Article.objects.filter(blahblah=99) - - def test_lookup_collision(self): - """ - Ensure that genuine field names don't collide with built-in lookup - types ('year', 'gt', 'range', 'in' etc.). - Refs #11670. - """ - - # Here we're using 'gt' as a code number for the year, e.g. 111=>2009. - season_2009 = Season.objects.create(year=2009, gt=111) - season_2009.games.create(home="Houston Astros", away="St. Louis Cardinals") - season_2010 = Season.objects.create(year=2010, gt=222) - season_2010.games.create(home="Houston Astros", away="Chicago Cubs") - season_2010.games.create(home="Houston Astros", away="Milwaukee Brewers") - season_2010.games.create(home="Houston Astros", away="St. Louis Cardinals") - season_2011 = Season.objects.create(year=2011, gt=333) - season_2011.games.create(home="Houston Astros", away="St. Louis Cardinals") - season_2011.games.create(home="Houston Astros", away="Milwaukee Brewers") - hunter_pence = Player.objects.create(name="Hunter Pence") - hunter_pence.games = Game.objects.filter(season__year__in=[2009, 2010]) - pudge = Player.objects.create(name="Ivan Rodriquez") - pudge.games = Game.objects.filter(season__year=2009) - pedro_feliz = Player.objects.create(name="Pedro Feliz") - pedro_feliz.games = Game.objects.filter(season__year__in=[2011]) - johnson = Player.objects.create(name="Johnson") - johnson.games = Game.objects.filter(season__year__in=[2011]) - - # Games in 2010 - self.assertEqual(Game.objects.filter(season__year=2010).count(), 3) - self.assertEqual(Game.objects.filter(season__year__exact=2010).count(), 3) - self.assertEqual(Game.objects.filter(season__gt=222).count(), 3) - self.assertEqual(Game.objects.filter(season__gt__exact=222).count(), 3) - - # Games in 2011 - self.assertEqual(Game.objects.filter(season__year=2011).count(), 2) - self.assertEqual(Game.objects.filter(season__year__exact=2011).count(), 2) - self.assertEqual(Game.objects.filter(season__gt=333).count(), 2) - self.assertEqual(Game.objects.filter(season__gt__exact=333).count(), 2) - self.assertEqual(Game.objects.filter(season__year__gt=2010).count(), 2) - self.assertEqual(Game.objects.filter(season__gt__gt=222).count(), 2) - - # Games played in 2010 and 2011 - self.assertEqual(Game.objects.filter(season__year__in=[2010, 2011]).count(), 5) - self.assertEqual(Game.objects.filter(season__year__gt=2009).count(), 5) - self.assertEqual(Game.objects.filter(season__gt__in=[222, 333]).count(), 5) - self.assertEqual(Game.objects.filter(season__gt__gt=111).count(), 5) - - # Players who played in 2009 - self.assertEqual(Player.objects.filter(games__season__year=2009).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__year__exact=2009).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__gt=111).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__gt__exact=111).distinct().count(), 2) - - # Players who played in 2010 - self.assertEqual(Player.objects.filter(games__season__year=2010).distinct().count(), 1) - self.assertEqual(Player.objects.filter(games__season__year__exact=2010).distinct().count(), 1) - self.assertEqual(Player.objects.filter(games__season__gt=222).distinct().count(), 1) - self.assertEqual(Player.objects.filter(games__season__gt__exact=222).distinct().count(), 1) - - # Players who played in 2011 - self.assertEqual(Player.objects.filter(games__season__year=2011).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__year__exact=2011).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__gt=333).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__year__gt=2010).distinct().count(), 2) - self.assertEqual(Player.objects.filter(games__season__gt__gt=222).distinct().count(), 2) diff --git a/tests/modeltests/test_client/tests.py b/tests/modeltests/test_client/tests.py deleted file mode 100644 index 6ad51ac9a0..0000000000 --- a/tests/modeltests/test_client/tests.py +++ /dev/null @@ -1,21 +0,0 @@ -# Validate that you can override the default test suite - -from django.utils import unittest - - -def suite(): - """ - Define a suite that deliberately ignores a test defined in - this module. - """ - - testSuite = unittest.TestSuite() - testSuite.addTest(SampleTests('testGoodStuff')) - return testSuite - -class SampleTests(unittest.TestCase): - def testGoodStuff(self): - pass - - def testBadStuff(self): - self.fail("This test shouldn't run") diff --git a/tests/modeltests/transactions/tests.py b/tests/modeltests/transactions/tests.py deleted file mode 100644 index f3246eef2a..0000000000 --- a/tests/modeltests/transactions/tests.py +++ /dev/null @@ -1,310 +0,0 @@ -from __future__ import absolute_import - -from django.db import connection, transaction, IntegrityError -from django.test import TransactionTestCase, skipUnlessDBFeature - -from .models import Reporter - - -class TransactionTests(TransactionTestCase): - def create_a_reporter_then_fail(self, first, last): - a = Reporter(first_name=first, last_name=last) - a.save() - raise Exception("I meant to do that") - - def remove_a_reporter(self, first_name): - r = Reporter.objects.get(first_name="Alice") - r.delete() - - def manually_managed(self): - r = Reporter(first_name="Dirk", last_name="Gently") - r.save() - transaction.commit() - - def manually_managed_mistake(self): - r = Reporter(first_name="Edward", last_name="Woodward") - r.save() - # Oops, I forgot to commit/rollback! - - @skipUnlessDBFeature('supports_transactions') - def test_autocommit(self): - """ - The default behavior is to autocommit after each save() action. - """ - self.assertRaises(Exception, - self.create_a_reporter_then_fail, - "Alice", "Smith" - ) - - # The object created before the exception still exists - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_autocommit_decorator(self): - """ - The autocommit decorator works exactly the same as the default behavior. - """ - autocomitted_create_then_fail = transaction.autocommit( - self.create_a_reporter_then_fail - ) - self.assertRaises(Exception, - autocomitted_create_then_fail, - "Alice", "Smith" - ) - # Again, the object created before the exception still exists - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_autocommit_decorator_with_using(self): - """ - The autocommit decorator also works with a using argument. - """ - autocomitted_create_then_fail = transaction.autocommit(using='default')( - self.create_a_reporter_then_fail - ) - self.assertRaises(Exception, - autocomitted_create_then_fail, - "Alice", "Smith" - ) - # Again, the object created before the exception still exists - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success(self): - """ - With the commit_on_success decorator, the transaction is only committed - if the function doesn't throw an exception. - """ - committed_on_success = transaction.commit_on_success( - self.create_a_reporter_then_fail) - self.assertRaises(Exception, committed_on_success, "Dirk", "Gently") - # This time the object never got saved - self.assertEqual(Reporter.objects.count(), 0) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success_with_using(self): - """ - The commit_on_success decorator also works with a using argument. - """ - using_committed_on_success = transaction.commit_on_success(using='default')( - self.create_a_reporter_then_fail - ) - self.assertRaises(Exception, - using_committed_on_success, - "Dirk", "Gently" - ) - # This time the object never got saved - self.assertEqual(Reporter.objects.count(), 0) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success_succeed(self): - """ - If there aren't any exceptions, the data will get saved. - """ - Reporter.objects.create(first_name="Alice", last_name="Smith") - remove_comitted_on_success = transaction.commit_on_success( - self.remove_a_reporter - ) - remove_comitted_on_success("Alice") - self.assertEqual(list(Reporter.objects.all()), []) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success_exit(self): - @transaction.autocommit() - def gen_reporter(): - @transaction.commit_on_success - def create_reporter(): - Reporter.objects.create(first_name="Bobby", last_name="Tables") - - create_reporter() - # Much more formal - r = Reporter.objects.get() - r.first_name = "Robert" - r.save() - - gen_reporter() - r = Reporter.objects.get() - self.assertEqual(r.first_name, "Robert") - - - @skipUnlessDBFeature('supports_transactions') - def test_manually_managed(self): - """ - You can manually manage transactions if you really want to, but you - have to remember to commit/rollback. - """ - manually_managed = transaction.commit_manually(self.manually_managed) - manually_managed() - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_manually_managed_mistake(self): - """ - If you forget, you'll get bad errors. - """ - manually_managed_mistake = transaction.commit_manually( - self.manually_managed_mistake - ) - self.assertRaises(transaction.TransactionManagementError, - manually_managed_mistake) - - @skipUnlessDBFeature('supports_transactions') - def test_manually_managed_with_using(self): - """ - The commit_manually function also works with a using argument. - """ - using_manually_managed_mistake = transaction.commit_manually(using='default')( - self.manually_managed_mistake - ) - self.assertRaises(transaction.TransactionManagementError, - using_manually_managed_mistake - ) - - -class TransactionRollbackTests(TransactionTestCase): - def execute_bad_sql(self): - cursor = connection.cursor() - cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") - transaction.set_dirty() - - @skipUnlessDBFeature('requires_rollback_on_dirty_transaction') - def test_bad_sql(self): - """ - Regression for #11900: If a function wrapped by commit_on_success - writes a transaction that can't be committed, that transaction should - be rolled back. The bug is only visible using the psycopg2 backend, - though the fix is generally a good idea. - """ - execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql) - self.assertRaises(IntegrityError, execute_bad_sql) - transaction.rollback() - -class TransactionContextManagerTests(TransactionTestCase): - def create_reporter_and_fail(self): - Reporter.objects.create(first_name="Bob", last_name="Holtzman") - raise Exception - - @skipUnlessDBFeature('supports_transactions') - def test_autocommit(self): - """ - The default behavior is to autocommit after each save() action. - """ - with self.assertRaises(Exception): - self.create_reporter_and_fail() - # The object created before the exception still exists - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_autocommit_context_manager(self): - """ - The autocommit context manager works exactly the same as the default - behavior. - """ - with self.assertRaises(Exception): - with transaction.autocommit(): - self.create_reporter_and_fail() - - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_autocommit_context_manager_with_using(self): - """ - The autocommit context manager also works with a using argument. - """ - with self.assertRaises(Exception): - with transaction.autocommit(using="default"): - self.create_reporter_and_fail() - - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success(self): - """ - With the commit_on_success context manager, the transaction is only - committed if the block doesn't throw an exception. - """ - with self.assertRaises(Exception): - with transaction.commit_on_success(): - self.create_reporter_and_fail() - - self.assertEqual(Reporter.objects.count(), 0) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success_with_using(self): - """ - The commit_on_success context manager also works with a using argument. - """ - with self.assertRaises(Exception): - with transaction.commit_on_success(using="default"): - self.create_reporter_and_fail() - - self.assertEqual(Reporter.objects.count(), 0) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success_succeed(self): - """ - If there aren't any exceptions, the data will get saved. - """ - Reporter.objects.create(first_name="Alice", last_name="Smith") - with transaction.commit_on_success(): - Reporter.objects.filter(first_name="Alice").delete() - - self.assertQuerysetEqual(Reporter.objects.all(), []) - - @skipUnlessDBFeature('supports_transactions') - def test_commit_on_success_exit(self): - with transaction.autocommit(): - with transaction.commit_on_success(): - Reporter.objects.create(first_name="Bobby", last_name="Tables") - - # Much more formal - r = Reporter.objects.get() - r.first_name = "Robert" - r.save() - - r = Reporter.objects.get() - self.assertEqual(r.first_name, "Robert") - - @skipUnlessDBFeature('supports_transactions') - def test_manually_managed(self): - """ - You can manually manage transactions if you really want to, but you - have to remember to commit/rollback. - """ - with transaction.commit_manually(): - Reporter.objects.create(first_name="Libby", last_name="Holtzman") - transaction.commit() - self.assertEqual(Reporter.objects.count(), 1) - - @skipUnlessDBFeature('supports_transactions') - def test_manually_managed_mistake(self): - """ - If you forget, you'll get bad errors. - """ - with self.assertRaises(transaction.TransactionManagementError): - with transaction.commit_manually(): - Reporter.objects.create(first_name="Scott", last_name="Browning") - - @skipUnlessDBFeature('supports_transactions') - def test_manually_managed_with_using(self): - """ - The commit_manually function also works with a using argument. - """ - with self.assertRaises(transaction.TransactionManagementError): - with transaction.commit_manually(using="default"): - Reporter.objects.create(first_name="Walter", last_name="Cronkite") - - @skipUnlessDBFeature('requires_rollback_on_dirty_transaction') - def test_bad_sql(self): - """ - Regression for #11900: If a block wrapped by commit_on_success - writes a transaction that can't be committed, that transaction should - be rolled back. The bug is only visible using the psycopg2 backend, - though the fix is generally a good idea. - """ - with self.assertRaises(IntegrityError): - with transaction.commit_on_success(): - cursor = connection.cursor() - cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") - transaction.set_dirty() - transaction.rollback() diff --git a/tests/regressiontests/i18n/__init__.py b/tests/multiple_database/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/__init__.py +++ b/tests/multiple_database/__init__.py diff --git a/tests/regressiontests/multiple_database/fixtures/multidb-common.json b/tests/multiple_database/fixtures/multidb-common.json index 33134173b9..33134173b9 100644 --- a/tests/regressiontests/multiple_database/fixtures/multidb-common.json +++ b/tests/multiple_database/fixtures/multidb-common.json diff --git a/tests/regressiontests/multiple_database/fixtures/multidb.default.json b/tests/multiple_database/fixtures/multidb.default.json index 379b18a803..379b18a803 100644 --- a/tests/regressiontests/multiple_database/fixtures/multidb.default.json +++ b/tests/multiple_database/fixtures/multidb.default.json diff --git a/tests/regressiontests/multiple_database/fixtures/multidb.other.json b/tests/multiple_database/fixtures/multidb.other.json index c64f490201..c64f490201 100644 --- a/tests/regressiontests/multiple_database/fixtures/multidb.other.json +++ b/tests/multiple_database/fixtures/multidb.other.json diff --git a/tests/regressiontests/multiple_database/fixtures/pets.json b/tests/multiple_database/fixtures/pets.json index 89756a3e5b..89756a3e5b 100644 --- a/tests/regressiontests/multiple_database/fixtures/pets.json +++ b/tests/multiple_database/fixtures/pets.json diff --git a/tests/regressiontests/multiple_database/models.py b/tests/multiple_database/models.py index e46438649b..e46438649b 100644 --- a/tests/regressiontests/multiple_database/models.py +++ b/tests/multiple_database/models.py diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/multiple_database/tests.py index a903ea11a0..2bff7b3b66 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/multiple_database/tests.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals import datetime import pickle from operator import attrgetter +import warnings from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType @@ -1596,7 +1597,7 @@ class AuthTestCase(TestCase): 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) + self.assertTrue('"email": "alice@example.com"' in command_output) @override_settings(AUTH_PROFILE_MODULE='multiple_database.UserProfile') @@ -1612,8 +1613,10 @@ class UserProfileTestCase(TestCase): bob_profile = UserProfile(user=bob, flavor='crunchy frog') bob_profile.save() - self.assertEqual(alice.get_profile().flavor, 'chocolate') - self.assertEqual(bob.get_profile().flavor, 'crunchy frog') + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + self.assertEqual(alice.get_profile().flavor, 'chocolate') + self.assertEqual(bob.get_profile().flavor, 'crunchy frog') class AntiPetRouter(object): # A router that only expresses an opinion on syncdb, @@ -1758,6 +1761,11 @@ class SignalTests(TestCase): self.assertEqual(pre_delete_receiver._database, "other") self.assertEqual(post_delete_receiver._database, "other") + signals.pre_save.disconnect(sender=Person, receiver=pre_save_receiver) + signals.post_save.disconnect(sender=Person, receiver=post_save_receiver) + signals.pre_delete.disconnect(sender=Person, receiver=pre_delete_receiver) + signals.post_delete.disconnect(sender=Person, receiver=post_delete_receiver) + def test_database_arg_m2m(self): """ Test that the m2m_changed signal has a correct database arg (#13552) diff --git a/tests/regressiontests/i18n/contenttypes/__init__.py b/tests/mutually_referential/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/contenttypes/__init__.py +++ b/tests/mutually_referential/__init__.py diff --git a/tests/modeltests/mutually_referential/models.py b/tests/mutually_referential/models.py index 0f1bd65cb1..0f1bd65cb1 100644 --- a/tests/modeltests/mutually_referential/models.py +++ b/tests/mutually_referential/models.py diff --git a/tests/modeltests/mutually_referential/tests.py b/tests/mutually_referential/tests.py index b3deb0e75c..b3deb0e75c 100644 --- a/tests/modeltests/mutually_referential/tests.py +++ b/tests/mutually_referential/tests.py diff --git a/tests/regressiontests/i18n/other/__init__.py b/tests/nested_foreign_keys/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/other/__init__.py +++ b/tests/nested_foreign_keys/__init__.py diff --git a/tests/regressiontests/nested_foreign_keys/models.py b/tests/nested_foreign_keys/models.py index 50d447951b..50d447951b 100644 --- a/tests/regressiontests/nested_foreign_keys/models.py +++ b/tests/nested_foreign_keys/models.py diff --git a/tests/regressiontests/nested_foreign_keys/tests.py b/tests/nested_foreign_keys/tests.py index a976d12453..5cb23cfb9c 100644 --- a/tests/regressiontests/nested_foreign_keys/tests.py +++ b/tests/nested_foreign_keys/tests.py @@ -71,6 +71,12 @@ class NestedForeignKeysTests(TestCase): self.assertEqual(Event.objects.filter(screeningnullfk__movie=self.movie).count(), 1) self.assertEqual(Event.objects.exclude(screeningnullfk__movie=self.movie).count(), 2) + def test_null_exclude(self): + screening = ScreeningNullFK.objects.create(movie=None) + ScreeningNullFK.objects.create(movie=self.movie) + self.assertEqual( + list(ScreeningNullFK.objects.exclude(movie__id=self.movie.pk)), + [screening]) # This test failed in #16715 because in some cases INNER JOIN was selected # for the second foreign key relation instead of LEFT OUTER JOIN. diff --git a/tests/regressiontests/i18n/other/locale/__init__.py b/tests/null_fk/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/other/locale/__init__.py +++ b/tests/null_fk/__init__.py diff --git a/tests/regressiontests/null_fk/models.py b/tests/null_fk/models.py index c86ee8a5a9..c86ee8a5a9 100644 --- a/tests/regressiontests/null_fk/models.py +++ b/tests/null_fk/models.py diff --git a/tests/regressiontests/null_fk/tests.py b/tests/null_fk/tests.py index 96a06b6769..96a06b6769 100644 --- a/tests/regressiontests/null_fk/tests.py +++ b/tests/null_fk/tests.py diff --git a/tests/regressiontests/i18n/other/locale/de/__init__.py b/tests/null_fk_ordering/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/other/locale/de/__init__.py +++ b/tests/null_fk_ordering/__init__.py diff --git a/tests/regressiontests/null_fk_ordering/models.py b/tests/null_fk_ordering/models.py index 3caff0d594..3caff0d594 100644 --- a/tests/regressiontests/null_fk_ordering/models.py +++ b/tests/null_fk_ordering/models.py diff --git a/tests/regressiontests/null_fk_ordering/tests.py b/tests/null_fk_ordering/tests.py index aea969de4f..aea969de4f 100644 --- a/tests/regressiontests/null_fk_ordering/tests.py +++ b/tests/null_fk_ordering/tests.py diff --git a/tests/regressiontests/i18n/patterns/__init__.py b/tests/null_queries/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/patterns/__init__.py +++ b/tests/null_queries/__init__.py diff --git a/tests/regressiontests/null_queries/models.py b/tests/null_queries/models.py index 25560fbab7..9070dd4873 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/null_queries/models.py @@ -28,4 +28,5 @@ class OuterB(models.Model): class Inner(models.Model): first = models.ForeignKey(OuterA) - second = models.ForeignKey(OuterB, null=True) + # second would clash with the __second lookup. + third = models.ForeignKey(OuterB, null=True) diff --git a/tests/regressiontests/null_queries/tests.py b/tests/null_queries/tests.py index 47c99fbcb3..93e72d55d8 100644 --- a/tests/regressiontests/null_queries/tests.py +++ b/tests/null_queries/tests.py @@ -55,17 +55,17 @@ class NullQueriesTests(TestCase): """ obj = OuterA.objects.create() self.assertQuerysetEqual( - OuterA.objects.filter(inner__second=None), + OuterA.objects.filter(inner__third=None), ['<OuterA: OuterA object>'] ) self.assertQuerysetEqual( - OuterA.objects.filter(inner__second__data=None), + OuterA.objects.filter(inner__third__data=None), ['<OuterA: OuterA object>'] ) inner_obj = Inner.objects.create(first=obj) self.assertQuerysetEqual( - Inner.objects.filter(first__inner__second=None), + Inner.objects.filter(first__inner__third=None), ['<Inner: Inner object>'] ) diff --git a/tests/regressiontests/i18n/patterns/urls/__init__.py b/tests/one_to_one/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/patterns/urls/__init__.py +++ b/tests/one_to_one/__init__.py diff --git a/tests/modeltests/one_to_one/models.py b/tests/one_to_one/models.py index 9599496cb7..9599496cb7 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/one_to_one/models.py diff --git a/tests/modeltests/one_to_one/tests.py b/tests/one_to_one/tests.py index 6ee78520bd..6ee78520bd 100644 --- a/tests/modeltests/one_to_one/tests.py +++ b/tests/one_to_one/tests.py diff --git a/tests/regressiontests/i18n/resolution/__init__.py b/tests/one_to_one_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/i18n/resolution/__init__.py +++ b/tests/one_to_one_regress/__init__.py diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/one_to_one_regress/models.py index 38b801f40e..058baafc82 100644 --- a/tests/regressiontests/one_to_one_regress/models.py +++ b/tests/one_to_one_regress/models.py @@ -24,14 +24,14 @@ class Restaurant(models.Model): @python_2_unicode_compatible class Bar(models.Model): place = models.OneToOneField(Place) - serves_cocktails = models.BooleanField() + serves_cocktails = models.BooleanField(default=True) def __str__(self): return "%s the bar" % self.place.name class UndergroundBar(models.Model): place = models.OneToOneField(Place, null=True) - serves_cocktails = models.BooleanField() + serves_cocktails = models.BooleanField(default=True) @python_2_unicode_compatible class Favorites(models.Model): diff --git a/tests/regressiontests/one_to_one_regress/tests.py b/tests/one_to_one_regress/tests.py index 615536ba38..615536ba38 100644 --- a/tests/regressiontests/one_to_one_regress/tests.py +++ b/tests/one_to_one_regress/tests.py diff --git a/tests/regressiontests/indexes/__init__.py b/tests/or_lookups/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/indexes/__init__.py +++ b/tests/or_lookups/__init__.py diff --git a/tests/modeltests/or_lookups/models.py b/tests/or_lookups/models.py index f146b2e72d..f146b2e72d 100644 --- a/tests/modeltests/or_lookups/models.py +++ b/tests/or_lookups/models.py diff --git a/tests/modeltests/or_lookups/tests.py b/tests/or_lookups/tests.py index e1c6fcb32a..e1c6fcb32a 100644 --- a/tests/modeltests/or_lookups/tests.py +++ b/tests/or_lookups/tests.py diff --git a/tests/regressiontests/initial_sql_regress/__init__.py b/tests/order_with_respect_to/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/initial_sql_regress/__init__.py +++ b/tests/order_with_respect_to/__init__.py diff --git a/tests/modeltests/order_with_respect_to/models.py b/tests/order_with_respect_to/models.py index 06bb56b141..06bb56b141 100644 --- a/tests/modeltests/order_with_respect_to/models.py +++ b/tests/order_with_respect_to/models.py diff --git a/tests/modeltests/order_with_respect_to/tests.py b/tests/order_with_respect_to/tests.py index 559cb1d996..559cb1d996 100644 --- a/tests/modeltests/order_with_respect_to/tests.py +++ b/tests/order_with_respect_to/tests.py diff --git a/tests/regressiontests/inline_formsets/__init__.py b/tests/ordering/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/inline_formsets/__init__.py +++ b/tests/ordering/__init__.py diff --git a/tests/modeltests/ordering/models.py b/tests/ordering/models.py index 67126e1bda..67126e1bda 100644 --- a/tests/modeltests/ordering/models.py +++ b/tests/ordering/models.py diff --git a/tests/modeltests/ordering/tests.py b/tests/ordering/tests.py index b1b5253682..b1b5253682 100644 --- a/tests/modeltests/ordering/tests.py +++ b/tests/ordering/tests.py diff --git a/tests/regressiontests/introspection/__init__.py b/tests/pagination/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/introspection/__init__.py +++ b/tests/pagination/__init__.py diff --git a/tests/regressiontests/pagination/custom.py b/tests/pagination/custom.py index 47a932c7e3..47a932c7e3 100644 --- a/tests/regressiontests/pagination/custom.py +++ b/tests/pagination/custom.py diff --git a/tests/regressiontests/pagination/models.py b/tests/pagination/models.py index 9dc8d4b776..9dc8d4b776 100644 --- a/tests/regressiontests/pagination/models.py +++ b/tests/pagination/models.py diff --git a/tests/regressiontests/pagination/tests.py b/tests/pagination/tests.py index dea5756672..dea5756672 100644 --- a/tests/regressiontests/pagination/tests.py +++ b/tests/pagination/tests.py diff --git a/tests/regressiontests/localflavor/__init__.py b/tests/prefetch_related/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/localflavor/__init__.py +++ b/tests/prefetch_related/__init__.py diff --git a/tests/modeltests/prefetch_related/models.py b/tests/prefetch_related/models.py index e58997d200..81c569844f 100644 --- a/tests/modeltests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -87,8 +87,8 @@ class Qualification(models.Model): class TeacherManager(models.Manager): - def get_query_set(self): - return super(TeacherManager, self).get_query_set().prefetch_related('qualifications') + def get_queryset(self): + return super(TeacherManager, self).get_queryset().prefetch_related('qualifications') @python_2_unicode_compatible diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index e81560f01f..e81560f01f 100644 --- a/tests/modeltests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py diff --git a/tests/regressiontests/localflavor/generic/__init__.py b/tests/properties/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/localflavor/generic/__init__.py +++ b/tests/properties/__init__.py diff --git a/tests/modeltests/properties/models.py b/tests/properties/models.py index 5c2656570c..5c2656570c 100644 --- a/tests/modeltests/properties/models.py +++ b/tests/properties/models.py diff --git a/tests/modeltests/properties/tests.py b/tests/properties/tests.py index 8a40d06e98..8a40d06e98 100644 --- a/tests/modeltests/properties/tests.py +++ b/tests/properties/tests.py diff --git a/tests/regressiontests/logging_tests/__init__.py b/tests/proxy_model_inheritance/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/logging_tests/__init__.py +++ b/tests/proxy_model_inheritance/__init__.py diff --git a/tests/regressiontests/m2m_regress/__init__.py b/tests/proxy_model_inheritance/app1/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/m2m_regress/__init__.py +++ b/tests/proxy_model_inheritance/app1/__init__.py diff --git a/tests/modeltests/proxy_model_inheritance/app1/models.py b/tests/proxy_model_inheritance/app1/models.py index affcf140ac..affcf140ac 100644 --- a/tests/modeltests/proxy_model_inheritance/app1/models.py +++ b/tests/proxy_model_inheritance/app1/models.py diff --git a/tests/regressiontests/managers_regress/__init__.py b/tests/proxy_model_inheritance/app2/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/managers_regress/__init__.py +++ b/tests/proxy_model_inheritance/app2/__init__.py diff --git a/tests/modeltests/proxy_model_inheritance/app2/models.py b/tests/proxy_model_inheritance/app2/models.py index 1ad46d1088..1ad46d1088 100644 --- a/tests/modeltests/proxy_model_inheritance/app2/models.py +++ b/tests/proxy_model_inheritance/app2/models.py diff --git a/tests/modeltests/proxy_model_inheritance/models.py b/tests/proxy_model_inheritance/models.py index ef9ac6b0d3..ef9ac6b0d3 100644 --- a/tests/modeltests/proxy_model_inheritance/models.py +++ b/tests/proxy_model_inheritance/models.py diff --git a/tests/modeltests/proxy_model_inheritance/tests.py b/tests/proxy_model_inheritance/tests.py index 239bc67809..239bc67809 100644 --- a/tests/modeltests/proxy_model_inheritance/tests.py +++ b/tests/proxy_model_inheritance/tests.py diff --git a/tests/regressiontests/many_to_one_regress/__init__.py b/tests/proxy_models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/many_to_one_regress/__init__.py +++ b/tests/proxy_models/__init__.py diff --git a/tests/proxy_models/fixtures/myhorses.json b/tests/proxy_models/fixtures/myhorses.json new file mode 100644 index 0000000000..15943064c8 --- /dev/null +++ b/tests/proxy_models/fixtures/myhorses.json @@ -0,0 +1,24 @@ +[ + { + "pk": 100, + "model": "proxy_models.BaseUser", + "fields": { + "name": "Django Pony" + } + }, + { + "pk": 100, + "model": "proxy_models.TrackerUser", + "fields": { + "status": "emperor" + } + }, + { + "pk": 100, + "model": "proxy_models.Issue", + "fields": { + "summary": "Pony's Issue", + "assignee": 100 + } + } +]
\ No newline at end of file diff --git a/tests/modeltests/proxy_models/fixtures/mypeople.json b/tests/proxy_models/fixtures/mypeople.json index d20c8f2a6e..d20c8f2a6e 100644 --- a/tests/modeltests/proxy_models/fixtures/mypeople.json +++ b/tests/proxy_models/fixtures/mypeople.json diff --git a/tests/modeltests/proxy_models/models.py b/tests/proxy_models/models.py index 6c962aadc8..ffb36657e1 100644 --- a/tests/modeltests/proxy_models/models.py +++ b/tests/proxy_models/models.py @@ -10,12 +10,12 @@ from django.utils.encoding import python_2_unicode_compatible # A couple of managers for testing managing overriding in proxy model cases. class PersonManager(models.Manager): - def get_query_set(self): - return super(PersonManager, self).get_query_set().exclude(name="fred") + def get_queryset(self): + return super(PersonManager, self).get_queryset().exclude(name="fred") class SubManager(models.Manager): - def get_query_set(self): - return super(SubManager, self).get_query_set().exclude(name="wilma") + def get_queryset(self): + return super(SubManager, self).get_queryset().exclude(name="wilma") @python_2_unicode_compatible class Person(models.Model): diff --git a/tests/modeltests/proxy_models/tests.py b/tests/proxy_models/tests.py index d1c95467ee..77d2ba9a74 100644 --- a/tests/modeltests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import copy from django.conf import settings +from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.core import management from django.core.exceptions import FieldError @@ -14,7 +15,7 @@ from django.test import TestCase from .models import (MyPerson, Person, StatusPerson, LowerStatusPerson, MyPersonProxy, Abstract, OtherPerson, User, UserProxy, UserProxyProxy, Country, State, StateProxy, TrackerUser, BaseUser, Bug, ProxyTrackerUser, - Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement) + Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement, Issue) class ProxyModelTests(TestCase): @@ -360,3 +361,22 @@ class ProxyModelTests(TestCase): management.call_command('loaddata', 'mypeople.json', verbosity=0, commit=False) p = MyPerson.objects.get(pk=100) self.assertEqual(p.name, 'Elvis Presley') + + +class ProxyModelAdminTests(TestCase): + fixtures = ['myhorses'] + + def test_cascade_delete_proxy_model_admin_warning(self): + """ + Test if admin gives warning about cascade deleting models referenced + to concrete model by deleting proxy object. + """ + tracker_user = TrackerUser.objects.all()[0] + base_user = BaseUser.objects.all()[0] + issue = Issue.objects.all()[0] + with self.assertNumQueries(7): + collector = admin.util.NestedObjects('default') + collector.collect(ProxyTrackerUser.objects.all()) + self.assertTrue(tracker_user in collector.edges.get(None, ())) + self.assertTrue(base_user in collector.edges.get(None, ())) + self.assertTrue(issue in collector.edges.get(tracker_user, ())) diff --git a/tests/regressiontests/middleware/__init__.py b/tests/queries/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/middleware/__init__.py +++ b/tests/queries/__init__.py diff --git a/tests/regressiontests/queries/models.py b/tests/queries/models.py index 73b9762150..71346d8be9 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/queries/models.py @@ -64,6 +64,7 @@ class Annotation(models.Model): class ExtraInfo(models.Model): info = models.CharField(max_length=100) note = models.ForeignKey(Note) + value = models.IntegerField(null=True) class Meta: ordering = ['info'] @@ -175,8 +176,8 @@ class LoopZ(models.Model): # A model and custom default manager combination. class CustomManager(models.Manager): - def get_query_set(self): - qs = super(CustomManager, self).get_query_set() + def get_queryset(self): + qs = super(CustomManager, self).get_queryset() return qs.filter(public=True, tag__name='t1') @python_2_unicode_compatible @@ -196,8 +197,8 @@ 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") + def get_queryset(self): + return super(MemberManager, self).get_queryset().select_related("details") class Member(models.Model): name = models.CharField(max_length=10) @@ -421,3 +422,78 @@ class Responsibility(models.Model): def __str__(self): return self.description + +# Models for disjunction join promotion low level testing. +class FK1(models.Model): + f1 = models.TextField() + f2 = models.TextField() + +class FK2(models.Model): + f1 = models.TextField() + f2 = models.TextField() + +class FK3(models.Model): + f1 = models.TextField() + f2 = models.TextField() + +class BaseA(models.Model): + a = models.ForeignKey(FK1, null=True) + b = models.ForeignKey(FK2, null=True) + c = models.ForeignKey(FK3, null=True) + +@python_2_unicode_compatible +class Identifier(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + +class Program(models.Model): + identifier = models.OneToOneField(Identifier) + +class Channel(models.Model): + programs = models.ManyToManyField(Program) + identifier = models.OneToOneField(Identifier) + +class Book(models.Model): + title = models.TextField() + chapter = models.ForeignKey('Chapter') + +class Chapter(models.Model): + title = models.TextField() + paragraph = models.ForeignKey('Paragraph') + + +class Paragraph(models.Model): + text = models.TextField() + page = models.ManyToManyField('Page') + +class Page(models.Model): + text = models.TextField() + +class MyObject(models.Model): + parent = models.ForeignKey('self', null=True, blank=True, related_name='children') + data = models.CharField(max_length=100) + created_at = models.DateTimeField(auto_now_add=True) + +# Models for #17600 regressions +@python_2_unicode_compatible +class Order(models.Model): + id = models.IntegerField(primary_key=True) + + class Meta: + ordering = ('pk', ) + + def __str__(self): + return '%s' % self.pk + +@python_2_unicode_compatible +class OrderItem(models.Model): + order = models.ForeignKey(Order, related_name='items') + status = models.IntegerField() + + class Meta: + ordering = ('pk', ) + + def __str__(self): + return '%s' % self.pk diff --git a/tests/regressiontests/queries/tests.py b/tests/queries/tests.py index 75e27769b4..cdc26248c9 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/queries/tests.py @@ -8,8 +8,8 @@ import sys 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.db.models import Count, F, Q +from django.db.models.query import ITER_CHUNK_SIZE from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature @@ -23,8 +23,9 @@ from .models import (Annotation, Article, Author, Celebrity, Child, Cover, Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten, Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory, SpecialCategory, OneToOneCategory, NullableName, ProxyCategory, - SingleObject, RelatedObject, ModelA, ModelD, Responsibility, Job, - JobResponsibilities) + SingleObject, RelatedObject, ModelA, ModelB, ModelC, ModelD, Responsibility, + Job, JobResponsibilities, BaseA, Identifier, Program, Channel, Page, + Paragraph, Chapter, Book, MyObject, Order, OrderItem) class BaseQuerysetTest(TestCase): @@ -52,8 +53,8 @@ class Queries1Tests(BaseQuerysetTest): # 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.e2 = ExtraInfo.objects.create(info='e2', note=n2, value=41) + e1 = ExtraInfo.objects.create(info='e1', note=self.n1, value=42) self.a1 = Author.objects.create(name='a1', num=1001, extra=e1) self.a2 = Author.objects.create(name='a2', num=2002, extra=e1) @@ -550,37 +551,37 @@ class Queries1Tests(BaseQuerysetTest): 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)) + self.assertEqual(Item.objects.datetimes('created', 'month').count(), 1) + self.assertEqual(Item.objects.datetimes('created', 'day').count(), 2) + self.assertEqual(len(Item.objects.datetimes('created', 'day')), 2) + self.assertEqual(Item.objects.datetimes('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}), + Item.objects.datetimes('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'), + Item.objects.extra(select={'a': 1}).datetimes('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]), + Item.objects.datetimes('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'), + Item.objects.extra(where=['name=%s'], params=[name]).datetimes('created', 'day'), ['datetime.datetime(2007, 12, 19, 0, 0)'] ) def test_ticket7155(self): # Nullable dates self.assertQuerysetEqual( - Item.objects.dates('modified', 'day'), + Item.objects.datetimes('modified', 'day'), ['datetime.datetime(2007, 12, 19, 0, 0)'] ) @@ -663,31 +664,32 @@ class Queries1Tests(BaseQuerysetTest): 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'), []) + Eaten.objects.create(meal='m') + q = Eaten.objects.none() + with self.assertNumQueries(0): + self.assertQuerysetEqual(q.all(), []) + self.assertQuerysetEqual(q.filter(meal='m'), []) + self.assertQuerysetEqual(q.exclude(meal='m'), []) + self.assertQuerysetEqual(q.complex_filter({'pk': 1}), []) + self.assertQuerysetEqual(q.select_related('food'), []) + self.assertQuerysetEqual(q.annotate(Count('food')), []) + self.assertQuerysetEqual(q.order_by('meal', 'food'), []) + self.assertQuerysetEqual(q.distinct(), []) + self.assertQuerysetEqual( + q.extra(select={'foo': "1"}), + [] + ) + q.query.low_mark = 1 + self.assertRaisesMessage( + AssertionError, + 'Cannot change a query once a slice has been taken', + q.extra, select={'foo': "1"} + ) + self.assertQuerysetEqual(q.reverse(), []) + self.assertQuerysetEqual(q.defer('meal'), []) + self.assertQuerysetEqual(q.only('meal'), []) def test_ticket7791(self): # There were "issues" when ordering and distinct-ing on fields related @@ -698,7 +700,7 @@ class Queries1Tests(BaseQuerysetTest): ) # Pickling of DateQuerySets used to fail - qs = Item.objects.dates('created', 'month') + qs = Item.objects.datetimes('created', 'month') _ = pickle.loads(pickle.dumps(qs)) def test_ticket9997(self): @@ -832,7 +834,6 @@ class Queries1Tests(BaseQuerysetTest): 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]]]), @@ -878,7 +879,6 @@ class Queries1Tests(BaseQuerysetTest): Item.objects.filter(Q(tags__name='t4')), [repr(i) for i in Item.objects.filter(~Q(~Q(tags__name='t4')))]) - @unittest.expectedFailure def test_exclude_in(self): self.assertQuerysetEqual( Item.objects.exclude(Q(tags__name__in=['t4', 't3'])), @@ -1105,6 +1105,13 @@ class Queries1Tests(BaseQuerysetTest): self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 1) self.assertTrue(str(q3.query).count('INNER JOIN') == 0) + def test_ticket19672(self): + self.assertQuerysetEqual( + Report.objects.filter(Q(creator__isnull=False) & + ~Q(creator__extra__value=41)), + ['<Report: r1>'] + ) + class Queries2Tests(TestCase): def setUp(self): @@ -1234,8 +1241,8 @@ class Queries3Tests(BaseQuerysetTest): # field self.assertRaisesMessage( AssertionError, - "'name' isn't a DateField.", - Item.objects.dates, 'name', 'month' + "'name' isn't a DateTimeField.", + Item.objects.datetimes, 'name', 'month' ) class Queries4Tests(BaseQuerysetTest): @@ -1292,9 +1299,9 @@ class Queries4Tests(BaseQuerysetTest): q1 = Author.objects.filter(report__name='r5') q2 = Author.objects.filter(report__name='r4').filter(report__name='r1') combined = q1|q2 - self.assertEquals(str(combined.query).count('JOIN'), 2) - self.assertEquals(len(combined), 1) - self.assertEquals(combined[0].name, 'a1') + self.assertEqual(str(combined.query).count('JOIN'), 2) + self.assertEqual(len(combined), 1) + self.assertEqual(combined[0].name, 'a1') def test_ticket7095(self): # Updates that are filtered on the model being updated are somewhat @@ -1643,8 +1650,8 @@ class NullableRelOrderingTests(TestCase): # and that join must be LEFT join. The already existing join to related # objects must be kept INNER. So, we have both a INNER and a LEFT join # in the query. - self.assertEquals(str(qs.query).count('LEFT'), 1) - self.assertEquals(str(qs.query).count('INNER'), 1) + self.assertEqual(str(qs.query).count('LEFT'), 1) + self.assertEqual(str(qs.query).count('INNER'), 1) self.assertQuerysetEqual( qs, ['<Plaything: p2>'] @@ -1778,7 +1785,6 @@ class Queries6Tests(TestCase): # 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>'] @@ -1918,6 +1924,7 @@ class SubqueryTests(TestCase): 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') @@ -1932,11 +1939,44 @@ class CloneTests(TestCase): # that query in a way that involves cloning. self.assertEqual(ExtraInfo.objects.filter(note__in=n_list)[0].info, 'good') + def test_no_model_options_cloning(self): + """ + Test that cloning a queryset does not get out of hand. While complete + testing is impossible, this is a sanity check against invalid use of + deepcopy. refs #16759. + """ + opts_class = type(Note._meta) + note_deepcopy = getattr(opts_class, "__deepcopy__", None) + opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model options shouldn't be cloned.") + try: + Note.objects.filter(pk__lte=F('pk') + 1).all() + finally: + if note_deepcopy is None: + delattr(opts_class, "__deepcopy__") + else: + opts_class.__deepcopy__ = note_deepcopy + + def test_no_fields_cloning(self): + """ + Test that cloning a queryset does not get out of hand. While complete + testing is impossible, this is a sanity check against invalid use of + deepcopy. refs #16759. + """ + opts_class = type(Note._meta.get_field_by_name("misc")[0]) + note_deepcopy = getattr(opts_class, "__deepcopy__", None) + opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model fields shouldn't be cloned") + try: + Note.objects.filter(note=F('misc')).all() + finally: + if note_deepcopy is None: + delattr(opts_class, "__deepcopy__") + else: + opts_class.__deepcopy__ = note_deepcopy class EmptyQuerySetTests(TestCase): def test_emptyqueryset_values(self): - # #14366 -- Calling .values() on an EmptyQuerySet and then cloning that - # should not cause an error" + # #14366 -- Calling .values() on an empty QuerySet and then cloning + # that should not cause an error self.assertQuerysetEqual( Number.objects.none().values('num').order_by('num'), [] ) @@ -1952,9 +1992,9 @@ class EmptyQuerySetTests(TestCase): ) def test_ticket_19151(self): - # #19151 -- Calling .values() or .values_list() on an EmptyQuerySet - # should return EmptyQuerySet and not cause an error. - q = EmptyQuerySet() + # #19151 -- Calling .values() or .values_list() on an empty QuerySet + # should return an empty QuerySet and not cause an error. + q = Author.objects.none() self.assertQuerysetEqual(q.values(), []) self.assertQuerysetEqual(q.values_list(), []) @@ -2134,8 +2174,10 @@ class ConditionalTests(BaseQuerysetTest): @skipUnlessDBFeature('supports_1000_query_parameters') def test_ticket14244(self): # Test that the "in" lookup works with lists of 1000 items or more. + # The numbers amount is picked to force three different IN batches + # for Oracle, yet to be less than 2100 parameter limit for MSSQL. + numbers = range(2050) Number.objects.all().delete() - numbers = range(2500) Number.objects.bulk_create(Number(num=num) for num in numbers) self.assertEqual( Number.objects.filter(num__in=numbers[:1000]).count(), @@ -2151,7 +2193,7 @@ class ConditionalTests(BaseQuerysetTest): ) self.assertEqual( Number.objects.filter(num__in=numbers).count(), - 2500 + len(numbers) ) @@ -2246,6 +2288,103 @@ class ExcludeTest(TestCase): Responsibility.objects.exclude(jobs__name='Manager'), ['<Responsibility: Programming>']) +class ExcludeTest17600(TestCase): + """ + Some regressiontests for ticket #17600. Some of these likely duplicate + other existing tests. + """ + + def setUp(self): + # Create a few Orders. + self.o1 = Order.objects.create(pk=1) + self.o2 = Order.objects.create(pk=2) + self.o3 = Order.objects.create(pk=3) + + # Create some OrderItems for the first order with homogeneous + # status_id values + self.oi1 = OrderItem.objects.create(order=self.o1, status=1) + self.oi2 = OrderItem.objects.create(order=self.o1, status=1) + self.oi3 = OrderItem.objects.create(order=self.o1, status=1) + + # Create some OrderItems for the second order with heterogeneous + # status_id values + self.oi4 = OrderItem.objects.create(order=self.o2, status=1) + self.oi5 = OrderItem.objects.create(order=self.o2, status=2) + self.oi6 = OrderItem.objects.create(order=self.o2, status=3) + + # Create some OrderItems for the second order with heterogeneous + # status_id values + self.oi7 = OrderItem.objects.create(order=self.o3, status=2) + self.oi8 = OrderItem.objects.create(order=self.o3, status=3) + self.oi9 = OrderItem.objects.create(order=self.o3, status=4) + + def test_exclude_plain(self): + """ + This should exclude Orders which have some items with status 1 + + """ + self.assertQuerysetEqual( + Order.objects.exclude(items__status=1), + ['<Order: 3>']) + + def test_exclude_plain_distinct(self): + """ + This should exclude Orders which have some items with status 1 + + """ + self.assertQuerysetEqual( + Order.objects.exclude(items__status=1).distinct(), + ['<Order: 3>']) + + def test_exclude_with_q_object_distinct(self): + """ + This should exclude Orders which have some items with status 1 + + """ + self.assertQuerysetEqual( + Order.objects.exclude(Q(items__status=1)).distinct(), + ['<Order: 3>']) + + def test_exclude_with_q_object_no_distinct(self): + """ + This should exclude Orders which have some items with status 1 + + """ + self.assertQuerysetEqual( + Order.objects.exclude(Q(items__status=1)), + ['<Order: 3>']) + + def test_exclude_with_q_is_equal_to_plain_exclude(self): + """ + Using exclude(condition) and exclude(Q(condition)) should + yield the same QuerySet + + """ + self.assertEqual( + list(Order.objects.exclude(items__status=1).distinct()), + list(Order.objects.exclude(Q(items__status=1)).distinct())) + + def test_exclude_with_q_is_equal_to_plain_exclude_variation(self): + """ + Using exclude(condition) and exclude(Q(condition)) should + yield the same QuerySet + + """ + self.assertEqual( + list(Order.objects.exclude(items__status=1)), + list(Order.objects.exclude(Q(items__status=1)).distinct())) + + @unittest.expectedFailure + def test_only_orders_with_all_items_having_status_1(self): + """ + This should only return orders having ALL items set to status 1, or + those items not having any orders at all. The correct way to write + this query in SQL seems to be using two nested subqueries. + """ + self.assertQuerysetEqual( + Order.objects.exclude(~Q(items__status=1)).distinct(), + ['<Order: 1>']) + class NullInExcludeTest(TestCase): def setUp(self): NullableName.objects.create(name='i1') @@ -2281,6 +2420,14 @@ class NullInExcludeTest(TestCase): NullableName.objects.exclude(name__in=[None]), ['i1'], attrgetter('name')) + def test_double_exclude(self): + self.assertEqual( + list(NullableName.objects.filter(~~Q(name='i1'))), + list(NullableName.objects.filter(Q(name='i1')))) + self.assertNotIn( + 'IS NOT NULL', + str(NullableName.objects.filter(~~Q(name='i1')).query)) + class EmptyStringsAsNullTest(TestCase): """ Test that filtering on non-null character fields works as expected. @@ -2388,8 +2535,12 @@ class WhereNodeTest(TestCase): class NullJoinPromotionOrTest(TestCase): def setUp(self): - d = ModelD.objects.create(name='foo') - ModelA.objects.create(name='bar', d=d) + self.d1 = ModelD.objects.create(name='foo') + d2 = ModelD.objects.create(name='bar') + self.a1 = ModelA.objects.create(name='a1', d=self.d1) + c = ModelC.objects.create(name='c') + b = ModelB.objects.create(name='b', c=c) + self.a2 = ModelA.objects.create(name='a2', b=b, d=d2) def test_ticket_17886(self): # The first Q-object is generating the match, the rest of the filters @@ -2403,12 +2554,38 @@ class NullJoinPromotionOrTest(TestCase): Q(b__c__name='foo') ) qset = ModelA.objects.filter(q_obj) - self.assertEqual(len(qset), 1) + self.assertEqual(list(qset), [self.a1]) # We generate one INNER JOIN to D. The join is direct and not nullable # so we can use INNER JOIN for it. However, we can NOT use INNER JOIN # for the b->c join, as a->b is nullable. self.assertEqual(str(qset.query).count('INNER JOIN'), 1) + def test_isnull_filter_promotion(self): + qs = ModelA.objects.filter(Q(b__name__isnull=True)) + self.assertEqual(str(qs.query).count('LEFT OUTER'), 1) + self.assertEqual(list(qs), [self.a1]) + + qs = ModelA.objects.filter(~Q(b__name__isnull=True)) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(list(qs), [self.a2]) + + qs = ModelA.objects.filter(~~Q(b__name__isnull=True)) + self.assertEqual(str(qs.query).count('LEFT OUTER'), 1) + self.assertEqual(list(qs), [self.a1]) + + qs = ModelA.objects.filter(Q(b__name__isnull=False)) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(list(qs), [self.a2]) + + qs = ModelA.objects.filter(~Q(b__name__isnull=False)) + self.assertEqual(str(qs.query).count('LEFT OUTER'), 1) + self.assertEqual(list(qs), [self.a1]) + + qs = ModelA.objects.filter(~~Q(b__name__isnull=False)) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(list(qs), [self.a2]) + + class ReverseJoinTrimmingTest(TestCase): def test_reverse_trimming(self): # Check that we don't accidentally trim reverse joins - we can't know @@ -2417,4 +2594,240 @@ class ReverseJoinTrimmingTest(TestCase): t = Tag.objects.create() qs = Tag.objects.filter(annotation__tag=t.pk) self.assertIn('INNER JOIN', str(qs.query)) - self.assertEquals(list(qs), []) + self.assertEqual(list(qs), []) + +class JoinReuseTest(TestCase): + """ + Test that the queries reuse joins sensibly (for example, direct joins + are always reused). + """ + def test_fk_reuse(self): + qs = Annotation.objects.filter(tag__name='foo').filter(tag__name='bar') + self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_fk_reuse_select_related(self): + qs = Annotation.objects.filter(tag__name='foo').select_related('tag') + self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_fk_reuse_annotation(self): + qs = Annotation.objects.filter(tag__name='foo').annotate(cnt=Count('tag__name')) + self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_fk_reuse_disjunction(self): + qs = Annotation.objects.filter(Q(tag__name='foo') | Q(tag__name='bar')) + self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_fk_reuse_order_by(self): + qs = Annotation.objects.filter(tag__name='foo').order_by('tag__name') + self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_revo2o_reuse(self): + qs = Detail.objects.filter(member__name='foo').filter(member__name='foo') + self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_revfk_noreuse(self): + qs = Author.objects.filter(report__name='r4').filter(report__name='r1') + self.assertEqual(str(qs.query).count('JOIN'), 2) + +class DisjunctionPromotionTests(TestCase): + def test_disjunction_promotion1(self): + # Pre-existing join, add two ORed filters to the same join, + # all joins can be INNER JOINS. + qs = BaseA.objects.filter(a__f1='foo') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + qs = qs.filter(Q(b__f1='foo') | Q(b__f2='foo')) + self.assertEqual(str(qs.query).count('INNER JOIN'), 2) + # Reverse the order of AND and OR filters. + qs = BaseA.objects.filter(Q(b__f1='foo') | Q(b__f2='foo')) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + qs = qs.filter(a__f1='foo') + self.assertEqual(str(qs.query).count('INNER JOIN'), 2) + + def test_disjunction_promotion2(self): + qs = BaseA.objects.filter(a__f1='foo') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + # Now we have two different joins in an ORed condition, these + # must be OUTER joins. The pre-existing join should remain INNER. + qs = qs.filter(Q(b__f1='foo') | Q(c__f2='foo')) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 2) + # Reverse case. + qs = BaseA.objects.filter(Q(b__f1='foo') | Q(c__f2='foo')) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 2) + qs = qs.filter(a__f1='foo') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 2) + + def test_disjunction_promotion3(self): + qs = BaseA.objects.filter(a__f2='bar') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + # The ANDed a__f2 filter allows us to use keep using INNER JOIN + # even inside the ORed case. If the join to a__ returns nothing, + # the ANDed filter for a__f2 can't be true. + qs = qs.filter(Q(a__f1='foo') | Q(b__f2='foo')) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1) + + @unittest.expectedFailure + def test_disjunction_promotion3_failing(self): + # Now the ORed filter creates LOUTER join, but we do not have + # logic to unpromote it for the AND filter after it. The query + # results will be correct, but we have one LOUTER JOIN too much + # currently. + qs = BaseA.objects.filter( + Q(a__f1='foo') | Q(b__f2='foo')).filter(a__f2='bar') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1) + + def test_disjunction_promotion4(self): + qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + self.assertEqual(str(qs.query).count('JOIN'), 0) + qs = qs.filter(a__f1='foo') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + qs = BaseA.objects.filter(a__f1='foo') + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + qs = qs.filter(Q(a=1) | Q(a=2)) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + + def test_disjunction_promotion5(self): + qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + # Note that the above filters on a force the join to an + # inner join even if it is trimmed. + self.assertEqual(str(qs.query).count('JOIN'), 0) + qs = qs.filter(Q(a__f1='foo') | Q(b__f1='foo')) + # So, now the a__f1 join doesn't need promotion. + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1) + + @unittest.expectedFailure + def test_disjunction_promotion5_failing(self): + qs = BaseA.objects.filter(Q(a__f1='foo') | Q(b__f1='foo')) + # Now the join to a is created as LOUTER + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 0) + # The below filter should force the a to be inner joined. But, + # this is failing as we do not have join unpromotion logic. + qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1) + + def test_disjunction_promotion6(self): + qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + self.assertEqual(str(qs.query).count('JOIN'), 0) + qs = BaseA.objects.filter(Q(a__f1='foo') & Q(b__f1='foo')) + self.assertEqual(str(qs.query).count('INNER JOIN'), 2) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 0) + + qs = BaseA.objects.filter(Q(a__f1='foo') & Q(b__f1='foo')) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 0) + self.assertEqual(str(qs.query).count('INNER JOIN'), 2) + qs = qs.filter(Q(a=1) | Q(a=2)) + self.assertEqual(str(qs.query).count('INNER JOIN'), 2) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 0) + + def test_disjunction_promotion7(self): + qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + self.assertEqual(str(qs.query).count('JOIN'), 0) + qs = BaseA.objects.filter(Q(a__f1='foo') | (Q(b__f1='foo') & Q(a__f1='bar'))) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1) + qs = BaseA.objects.filter( + (Q(a__f1='foo') | Q(b__f1='foo')) & (Q(a__f1='bar') | Q(c__f1='foo')) + ) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 3) + self.assertEqual(str(qs.query).count('INNER JOIN'), 0) + qs = BaseA.objects.filter( + (Q(a__f1='foo') | (Q(a__f1='bar')) & (Q(b__f1='bar') | Q(c__f1='foo'))) + ) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 2) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + + def test_disjunction_promotion_fexpression(self): + qs = BaseA.objects.filter(Q(a__f1=F('b__f1')) | Q(b__f1='foo')) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 1) + self.assertEqual(str(qs.query).count('INNER JOIN'), 1) + qs = BaseA.objects.filter(Q(a__f1=F('c__f1')) | Q(b__f1='foo')) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 3) + qs = BaseA.objects.filter(Q(a__f1=F('b__f1')) | Q(a__f2=F('b__f2')) | Q(c__f1='foo')) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 3) + qs = BaseA.objects.filter(Q(a__f1=F('c__f1')) | (Q(pk=1) & Q(pk=2))) + self.assertEqual(str(qs.query).count('LEFT OUTER JOIN'), 2) + self.assertEqual(str(qs.query).count('INNER JOIN'), 0) + + +class ManyToManyExcludeTest(TestCase): + def test_exclude_many_to_many(self): + Identifier.objects.create(name='extra') + program = Program.objects.create(identifier=Identifier.objects.create(name='program')) + channel = Channel.objects.create(identifier=Identifier.objects.create(name='channel')) + channel.programs.add(program) + + # channel contains 'program1', so all Identifiers except that one + # should be returned + self.assertQuerysetEqual( + Identifier.objects.exclude(program__channel=channel).order_by('name'), + ['<Identifier: channel>', '<Identifier: extra>'] + ) + self.assertQuerysetEqual( + Identifier.objects.exclude(program__channel=None).order_by('name'), + ['<Identifier: program>'] + ) + + def test_ticket_12823(self): + pg3 = Page.objects.create(text='pg3') + pg2 = Page.objects.create(text='pg2') + pg1 = Page.objects.create(text='pg1') + pa1 = Paragraph.objects.create(text='pa1') + pa1.page = [pg1, pg2] + pa2 = Paragraph.objects.create(text='pa2') + pa2.page = [pg2, pg3] + pa3 = Paragraph.objects.create(text='pa3') + ch1 = Chapter.objects.create(title='ch1', paragraph=pa1) + ch2 = Chapter.objects.create(title='ch2', paragraph=pa2) + ch3 = Chapter.objects.create(title='ch3', paragraph=pa3) + b1 = Book.objects.create(title='b1', chapter=ch1) + b2 = Book.objects.create(title='b2', chapter=ch2) + b3 = Book.objects.create(title='b3', chapter=ch3) + q = Book.objects.exclude(chapter__paragraph__page__text='pg1') + self.assertNotIn('IS NOT NULL', str(q.query)) + self.assertEqual(len(q), 2) + self.assertNotIn(b1, q) + self.assertIn(b2, q) + self.assertIn(b3, q) + +class RelabelCloneTest(TestCase): + def test_ticket_19964(self): + my1 = MyObject.objects.create(data='foo') + my1.parent = my1 + my1.save() + my2 = MyObject.objects.create(data='bar', parent=my1) + parents = MyObject.objects.filter(parent=F('id')) + children = MyObject.objects.filter(parent__in=parents).exclude(parent=F('id')) + self.assertEqual(list(parents), [my1]) + # Evaluating the children query (which has parents as part of it) does + # not change results for the parents query. + self.assertEqual(list(children), [my2]) + self.assertEqual(list(parents), [my1]) + +class Ticket20101Tests(TestCase): + def test_ticket_20101(self): + """ + Tests QuerySet ORed combining in exclude subquery case. + """ + t = Tag.objects.create(name='foo') + a1 = Annotation.objects.create(tag=t, name='a1') + a2 = Annotation.objects.create(tag=t, name='a2') + a3 = Annotation.objects.create(tag=t, name='a3') + n = Note.objects.create(note='foo', misc='bar') + qs1 = Note.objects.exclude(annotation__in=[a1, a2]) + qs2 = Note.objects.filter(annotation__in=[a3]) + self.assertTrue(n in qs1) + self.assertFalse(n in qs2) + self.assertTrue(n in (qs1 | qs2)) + +class EmptyStringPromotionTests(TestCase): + def test_empty_string_promotion(self): + qs = RelatedObject.objects.filter(single__name='') + if connection.features.interprets_empty_strings_as_nulls: + self.assertIn('LEFT OUTER JOIN', str(qs.query)) + else: + self.assertNotIn('LEFT OUTER JOIN', str(qs.query)) diff --git a/tests/regressiontests/middleware_exceptions/__init__.py b/tests/queryset_pickle/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/middleware_exceptions/__init__.py +++ b/tests/queryset_pickle/__init__.py diff --git a/tests/regressiontests/queryset_pickle/models.py b/tests/queryset_pickle/models.py index d3500c903b..4bcfcfbf04 100644 --- a/tests/regressiontests/queryset_pickle/models.py +++ b/tests/queryset_pickle/models.py @@ -6,16 +6,16 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -def standalone_number(self): +def standalone_number(): return 1 class Numbers(object): @staticmethod - def get_static_number(self): + def get_static_number(): return 2 @classmethod - def get_class_number(self): + def get_class_number(cls): return 3 def get_member_number(self): diff --git a/tests/regressiontests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index ab32e8f647..da1bb8cb73 100644 --- a/tests/regressiontests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -9,6 +9,9 @@ from .models import Group, Event, Happening class PickleabilityTestCase(TestCase): + def setUp(self): + Happening.objects.create() # make sure the defaults are working (#20158) + def assert_pickles(self, qs): self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs)) diff --git a/tests/regressiontests/model_fields/__init__.py b/tests/raw_query/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_fields/__init__.py +++ b/tests/raw_query/__init__.py diff --git a/tests/modeltests/raw_query/fixtures/raw_query_books.json b/tests/raw_query/fixtures/raw_query_books.json index 35879aa5eb..35879aa5eb 100644 --- a/tests/modeltests/raw_query/fixtures/raw_query_books.json +++ b/tests/raw_query/fixtures/raw_query_books.json diff --git a/tests/modeltests/raw_query/models.py b/tests/raw_query/models.py index e7e221dc6e..e7e221dc6e 100644 --- a/tests/modeltests/raw_query/models.py +++ b/tests/raw_query/models.py diff --git a/tests/modeltests/raw_query/tests.py b/tests/raw_query/tests.py index e404c8b065..e404c8b065 100644 --- a/tests/modeltests/raw_query/tests.py +++ b/tests/raw_query/tests.py diff --git a/tests/regressiontests/builtin_server/tests.py b/tests/regressiontests/builtin_server/tests.py deleted file mode 100644 index c8dc77e42e..0000000000 --- a/tests/regressiontests/builtin_server/tests.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import unicode_literals - -from io import BytesIO - -from django.core.servers.basehttp import ServerHandler -from django.utils.unittest import TestCase - -# -# 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(str('200 OK'), [(str('Content-Type'), str('text/plain'))]) - return [b'Hello World!'] - -def wsgi_app_file_wrapper(environ, start_response): - start_response(str('200 OK'), [(str('Content-Type'), str('text/plain'))]) - return environ['wsgi.file_wrapper'](BytesIO(b'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'} - handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env) - handler.run(wsgi_app_file_wrapper) - self.assertTrue(handler._used_sendfile) - self.assertEqual(handler.stdout.getvalue(), b'') - self.assertEqual(handler.stderr.getvalue(), b'') - - def test_file_wrapper_no_sendfile(self): - env = {'SERVER_PROTOCOL': 'HTTP/1.0'} - handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env) - handler.run(wsgi_app) - self.assertFalse(handler._used_sendfile) - self.assertEqual(handler.stdout.getvalue().splitlines()[-1], b'Hello World!') - self.assertEqual(handler.stderr.getvalue(), b'') diff --git a/tests/regressiontests/comment_tests/custom_comments/__init__.py b/tests/regressiontests/comment_tests/custom_comments/__init__.py deleted file mode 100644 index 598927eace..0000000000 --- a/tests/regressiontests/comment_tests/custom_comments/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -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/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py deleted file mode 100644 index 6708e54c79..0000000000 --- a/tests/regressiontests/forms/tests/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import absolute_import - -from .error_messages import (FormsErrorMessagesTestCase, - ModelChoiceFieldErrorMessagesTestCase) -from .extra import FormsExtraTestCase, FormsExtraL10NTestCase -from .fields import FieldsTests -from .forms import FormsTestCase -from .formsets import (FormsFormsetTestCase, FormsetAsFooTests, - TestIsBoundBehavior, TestEmptyFormSet) -from .input_formats import (LocalizedTimeTests, CustomTimeInputFormatsTests, - SimpleTimeFormatTests, LocalizedDateTests, CustomDateInputFormatsTests, - SimpleDateFormatTests, LocalizedDateTimeTests, - CustomDateTimeInputFormatsTests, SimpleDateTimeFormatTests) -from .media import FormsMediaTestCase, StaticFormsMediaTestCase -from .models import (TestTicket12510, ModelFormCallableModelDefault, - FormsModelTestCase, RelatedModelFormTests) -from .regressions import FormsRegressionsTestCase -from .util import FormsUtilTestCase -from .validators import TestFieldWithValidators -from .widgets import (FormsWidgetTestCase, FormsI18NWidgetsTestCase, - WidgetTests, LiveWidgetTests, ClearableFileInputTests) diff --git a/tests/regressiontests/forms/tests/validators.py b/tests/regressiontests/forms/tests/validators.py deleted file mode 100644 index a4cb324815..0000000000 --- a/tests/regressiontests/forms/tests/validators.py +++ /dev/null @@ -1,16 +0,0 @@ -from django import forms -from django.core import validators -from django.core.exceptions import ValidationError -from django.utils.unittest import TestCase - - -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 as e: - self.assertEqual(2, len(e.messages)) diff --git a/tests/regressiontests/generic_views/tests.py b/tests/regressiontests/generic_views/tests.py deleted file mode 100644 index 3f80600efe..0000000000 --- a/tests/regressiontests/generic_views/tests.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import absolute_import - -from .base import (ViewTest, TemplateViewTest, RedirectViewTest, - GetContextDataTest) -from .dates import (ArchiveIndexViewTests, YearArchiveViewTests, - MonthArchiveViewTests, WeekArchiveViewTests, DayArchiveViewTests, - DateDetailViewTests) -from .detail import DetailViewTest -from .edit import (FormMixinTests, BasicFormTests, ModelFormMixinTests, - CreateViewTests, UpdateViewTests, DeleteViewTests) -from .list import ListViewTests diff --git a/tests/regressiontests/handlers/tests.py b/tests/regressiontests/handlers/tests.py deleted file mode 100644 index 8676a448d9..0000000000 --- a/tests/regressiontests/handlers/tests.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.core.handlers.wsgi import WSGIHandler -from django.test import RequestFactory -from django.test.utils import override_settings -from django.utils import unittest - -class HandlerTests(unittest.TestCase): - - # Mangle settings so the handler will fail - @override_settings(MIDDLEWARE_CLASSES=42) - def test_lock_safety(self): - """ - Tests for bug #11193 (errors inside middleware shouldn't leave - the initLock locked). - """ - # Try running the handler, it will fail in load_middleware - handler = WSGIHandler() - self.assertEqual(handler.initLock.locked(), False) - with self.assertRaises(Exception): - handler(None, None) - self.assertEqual(handler.initLock.locked(), False) - - def test_bad_path_info(self): - """Tests for bug #15672 ('request' referenced before assignment)""" - environ = RequestFactory().get('/').environ - environ['PATH_INFO'] = b'\xed' - handler = WSGIHandler() - response = handler(environ, lambda *a, **k: None) - self.assertEqual(response.status_code, 400) diff --git a/tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index 49a657b455..0000000000 --- a/tests/regressiontests/i18n/commands/locale/it/LC_MESSAGES/django.mo +++ /dev/null diff --git a/tests/regressiontests/i18n/commands/tests.py b/tests/regressiontests/i18n/commands/tests.py deleted file mode 100644 index e00ef72d59..0000000000 --- a/tests/regressiontests/i18n/commands/tests.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import re -from subprocess import Popen, PIPE - -from django.utils import six - -can_run_extraction_tests = False -can_run_compilation_tests = False - -def find_command(cmd, path=None, pathext=None): - if path is None: - path = os.environ.get('PATH', []).split(os.pathsep) - if isinstance(path, six.string_types): - 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): - can_run_extraction_tests = True - del p - -if find_command('msgfmt'): - can_run_compilation_tests = True diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo Binary files differdeleted file mode 100644 index f825e3918b..0000000000 --- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo +++ /dev/null diff --git a/tests/regressiontests/indexes/models.py b/tests/regressiontests/indexes/models.py deleted file mode 100644 index 9758377f99..0000000000 --- a/tests/regressiontests/indexes/models.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.db import models - - -class Article(models.Model): - headline = models.CharField(max_length=100) - pub_date = models.DateTimeField() - - class Meta: - index_together = [ - ["headline", "pub_date"], - ] diff --git a/tests/regressiontests/indexes/tests.py b/tests/regressiontests/indexes/tests.py deleted file mode 100644 index 0dac881fa9..0000000000 --- a/tests/regressiontests/indexes/tests.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.core.management.color import no_style -from django.db import connections, DEFAULT_DB_ALIAS -from django.test import TestCase - -from .models import Article - - -class IndexesTests(TestCase): - def test_index_together(self): - connection = connections[DEFAULT_DB_ALIAS] - index_sql = connection.creation.sql_indexes_for_model(Article, no_style()) - self.assertEqual(len(index_sql), 1) diff --git a/tests/regressiontests/initial_sql_regress/tests.py b/tests/regressiontests/initial_sql_regress/tests.py deleted file mode 100644 index 39d8921061..0000000000 --- a/tests/regressiontests/initial_sql_regress/tests.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.core.management.color import no_style -from django.core.management.sql import custom_sql_for_model -from django.db import connections, DEFAULT_DB_ALIAS -from django.test import TestCase - -from .models import Simple - - -class InitialSQLTests(TestCase): - # 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. - - def test_initial_sql(self): - # As pointed out by #14661, test data loaded by custom SQL - # can't be relied upon; as a result, the test framework flushes the - # data contents before every test. This test validates that this has - # occurred. - self.assertEqual(Simple.objects.count(), 0) - - def test_custom_sql(self): - # Simulate the custom SQL loading by syncdb - connection = connections[DEFAULT_DB_ALIAS] - custom_sql = custom_sql_for_model(Simple, no_style(), connection) - self.assertEqual(len(custom_sql), 8) - cursor = connection.cursor() - for sql in custom_sql: - cursor.execute(sql) - self.assertEqual(Simple.objects.count(), 8) diff --git a/tests/regressiontests/inspectdb/models.py b/tests/regressiontests/inspectdb/models.py deleted file mode 100644 index 4a6621402f..0000000000 --- a/tests/regressiontests/inspectdb/models.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.db import models - - -class People(models.Model): - name = models.CharField(max_length=255) - -class Message(models.Model): - from_field = models.ForeignKey(People, db_column='from_id') - -class PeopleData(models.Model): - people_pk = models.ForeignKey(People, primary_key=True) - ssn = models.CharField(max_length=11) - -class PeopleMoreData(models.Model): - people_unique = models.ForeignKey(People, unique=True) - license = models.CharField(max_length=255) - -class DigitsInColumnName(models.Model): - all_digits = models.CharField(max_length=11, db_column='123') - leading_digit = models.CharField(max_length=11, db_column='4extra') - leading_digits = models.CharField(max_length=11, db_column='45extra') - -class SpecialColumnName(models.Model): - field = models.IntegerField(db_column='field') - # Underscores - field_field_0 = models.IntegerField(db_column='Field_') - field_field_1 = models.IntegerField(db_column='Field__') - field_field_2 = models.IntegerField(db_column='__field') - # Other chars - prc_x = models.IntegerField(db_column='prc(%) x') diff --git a/tests/regressiontests/inspectdb/tests.py b/tests/regressiontests/inspectdb/tests.py deleted file mode 100644 index 028d263337..0000000000 --- a/tests/regressiontests/inspectdb/tests.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import unicode_literals - -from django.core.management import call_command -from django.db import connection -from django.test import TestCase, skipUnlessDBFeature -from django.utils.six import StringIO - - -class InspectDBTestCase(TestCase): - - def test_stealth_table_name_filter_option(self): - out = StringIO() - # Lets limit the introspection to tables created for models of this - # application - call_command('inspectdb', - table_name_filter=lambda tn:tn.startswith('inspectdb_'), - stdout=out) - error_message = "inspectdb has examined a table that should have been filtered out." - # contrib.contenttypes is one of the apps always installed when running - # the Django test suite, check that one of its tables hasn't been - # inspected - self.assertNotIn("class DjangoContentType(models.Model):", out.getvalue(), msg=error_message) - - @skipUnlessDBFeature('can_introspect_foreign_keys') - def test_attribute_name_not_python_keyword(self): - out = StringIO() - # Lets limit the introspection to tables created for models of this - # application - call_command('inspectdb', - table_name_filter=lambda tn:tn.startswith('inspectdb_'), - stdout=out) - output = out.getvalue() - error_message = "inspectdb generated an attribute name which is a python keyword" - self.assertNotIn("from = models.ForeignKey(InspectdbPeople)", output, msg=error_message) - # As InspectdbPeople model is defined after InspectdbMessage, it should be quoted - self.assertIn("from_field = models.ForeignKey('InspectdbPeople', db_column='from_id')", - output) - self.assertIn("people_pk = models.ForeignKey(InspectdbPeople, primary_key=True)", - output) - self.assertIn("people_unique = models.ForeignKey(InspectdbPeople, unique=True)", - output) - - def test_digits_column_name_introspection(self): - """Introspection of column names consist/start with digits (#16536/#17676)""" - out = StringIO() - # Lets limit the introspection to tables created for models of this - # application - call_command('inspectdb', - table_name_filter=lambda tn:tn.startswith('inspectdb_'), - stdout=out) - output = out.getvalue() - error_message = "inspectdb generated a model field name which is a number" - self.assertNotIn(" 123 = models.CharField", output, msg=error_message) - self.assertIn("number_123 = models.CharField", output) - - error_message = "inspectdb generated a model field name which starts with a digit" - self.assertNotIn(" 4extra = models.CharField", output, msg=error_message) - self.assertIn("number_4extra = models.CharField", output) - - self.assertNotIn(" 45extra = models.CharField", output, msg=error_message) - self.assertIn("number_45extra = models.CharField", output) - - def test_special_column_name_introspection(self): - """ - Introspection of column names containing special characters, - unsuitable for Python identifiers - """ - out = StringIO() - call_command('inspectdb', stdout=out) - output = out.getvalue() - base_name = 'Field' if connection.vendor != 'oracle' else 'field' - self.assertIn("field = models.IntegerField()", output) - self.assertIn("field_field = models.IntegerField(db_column='%s_')" % base_name, output) - self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output) - self.assertIn("field_field_1 = models.IntegerField(db_column='__field')", output) - self.assertIn("prc_x = models.IntegerField(db_column='prc(%) x')", output) diff --git a/tests/regressiontests/localflavor/generic/tests.py b/tests/regressiontests/localflavor/generic/tests.py deleted file mode 100644 index 4db26838fa..0000000000 --- a/tests/regressiontests/localflavor/generic/tests.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import unicode_literals - -import datetime - -from django.contrib.localflavor.generic.forms import DateField, DateTimeField - -from django.test import SimpleTestCase - - -class GenericLocalFlavorTests(SimpleTestCase): - def test_GenericDateField(self): - error_invalid = ['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 = ['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/tests/regressiontests/localflavor/tests.py b/tests/regressiontests/localflavor/tests.py deleted file mode 100644 index 52b771c405..0000000000 --- a/tests/regressiontests/localflavor/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import absolute_import - -from .generic.tests import GenericLocalFlavorTests diff --git a/tests/regressiontests/signals_regress/models.py b/tests/regressiontests/signals_regress/models.py deleted file mode 100644 index 829314c06c..0000000000 --- a/tests/regressiontests/signals_regress/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.db import models -from django.utils.encoding import python_2_unicode_compatible - - -@python_2_unicode_compatible -class Author(models.Model): - name = models.CharField(max_length=20) - - def __str__(self): - return self.name - -@python_2_unicode_compatible -class Book(models.Model): - name = models.CharField(max_length=20) - authors = models.ManyToManyField(Author) - - def __str__(self): - return self.name diff --git a/tests/regressiontests/signing/models.py b/tests/regressiontests/signing/models.py deleted file mode 100644 index 71abcc5198..0000000000 --- a/tests/regressiontests/signing/models.py +++ /dev/null @@ -1 +0,0 @@ -# models.py file for tests to run. diff --git a/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py b/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py deleted file mode 100644 index d1e4f3db5d..0000000000 --- a/tests/regressiontests/urlpatterns_reverse/erroneous_urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.conf.urls import patterns, url - -urlpatterns = patterns('', - # View has erroneous import - url(r'erroneous_inner/$', 'regressiontests.urlpatterns_reverse.views.erroneous_view'), - # Module has erroneous import - url(r'erroneous_outer/$', 'regressiontests.urlpatterns_reverse.erroneous_views_module.erroneous_view'), - # View does not exist - url(r'missing_inner/$', 'regressiontests.urlpatterns_reverse.views.missing_view'), - # View is not callable - url(r'uncallable/$', 'regressiontests.urlpatterns_reverse.views.uncallable'), - # Module does not exist - url(r'missing_outer/$', 'regressiontests.urlpatterns_reverse.missing_module.missing_view'), - # Regex contains an error (refs #6170) - url(r'(regex_error/$', 'regressiontestes.urlpatterns_reverse.views.empty_view'), -) diff --git a/tests/regressiontests/urlpatterns_reverse/middleware.py b/tests/regressiontests/urlpatterns_reverse/middleware.py deleted file mode 100644 index 03749e9570..0000000000 --- a/tests/regressiontests/urlpatterns_reverse/middleware.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import absolute_import - -from . 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/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py b/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py deleted file mode 100644 index 7a3e22078e..0000000000 --- a/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py +++ /dev/null @@ -1,8 +0,0 @@ -# Used by the ErrorHandlerResolutionTests test case. - -from django.conf.urls import patterns - -urlpatterns = patterns('') - -handler404 = 'regressiontests.urlpatterns_reverse.views.empty_view' -handler500 = 'regressiontests.urlpatterns_reverse.views.empty_view' diff --git a/tests/regressiontests/utils/models.py b/tests/regressiontests/utils/models.py deleted file mode 100644 index 97a72bab14..0000000000 --- a/tests/regressiontests/utils/models.py +++ /dev/null @@ -1 +0,0 @@ -# Test runner needs a models.py file. diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py deleted file mode 100644 index 11dd7c320e..0000000000 --- a/tests/regressiontests/utils/tests.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Tests for django.utils. -""" -from __future__ import absolute_import - -from .archive import TestBzip2Tar, TestGzipTar, TestTar, TestZip -from .baseconv import TestBaseConv -from .checksums import TestUtilsChecksums -from .crypto import TestUtilsCryptoMisc, TestUtilsCryptoPBKDF2 -from .datastructures import (DictWrapperTests, ImmutableListTests, - MergeDictTests, MultiValueDictTests, SortedDictTests) -from .dateformat import DateFormatTests -from .dateparse import DateParseTests -from .datetime_safe import DatetimeTests -from .decorators import DecoratorFromMiddlewareTests -from .encoding import TestEncodingUtils -from .feedgenerator import FeedgeneratorTest -from .functional import FunctionalTestCase -from .html import TestUtilsHtml -from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests -from .ipv6 import TestUtilsIPv6 -from .jslex import JsToCForGettextTest, JsTokensTest -from .module_loading import CustomLoader, DefaultLoader, EggLoader -from .numberformat import TestNumberFormat -from .os_utils import SafeJoinTests -from .regex_helper import NormalizeTests -from .simplelazyobject import TestUtilsSimpleLazyObject -from .termcolors import TermColorTests -from .text import TestUtilsText -from .timesince import TimesinceTests -from .timezone import TimezoneTests -from .tzinfo import TzinfoTests diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py deleted file mode 100644 index 17f3f4562d..0000000000 --- a/tests/regressiontests/views/tests/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import absolute_import - -from .debug import (DebugViewTests, ExceptionReporterTests, - ExceptionReporterTests, PlainTextReportTests, ExceptionReporterFilterTests, - AjaxResponseExceptionReporterFilter) -from .defaults import DefaultsTests -from .i18n import JsI18NTests, I18NTests, JsI18NTestsMultiPackage, JavascriptI18nTests -from .shortcuts import ShortcutTests -from .specials import URLHandling -from .static import StaticHelperTest, StaticUtilsTests, StaticTests diff --git a/tests/regressiontests/requests/__init__.py b/tests/requests/__init__.py index 3a328850c9..3a328850c9 100644 --- a/tests/regressiontests/requests/__init__.py +++ b/tests/requests/__init__.py diff --git a/tests/regressiontests/requests/models.py b/tests/requests/models.py index 19f81d601a..19f81d601a 100644 --- a/tests/regressiontests/requests/models.py +++ b/tests/requests/models.py diff --git a/tests/regressiontests/requests/tests.py b/tests/requests/tests.py index adf824dff7..daf426ea47 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/requests/tests.py @@ -6,17 +6,21 @@ import warnings from datetime import datetime, timedelta from io import BytesIO +from django.db import connection, connections, DEFAULT_DB_ALIAS +from django.core import signals from django.core.exceptions import SuspiciousOperation from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr, UnreadablePostError +from django.test import SimpleTestCase, TransactionTestCase from django.test.client import FakePayload from django.test.utils import override_settings, str_prefix -from django.utils import unittest +from django.utils import six +from django.utils.unittest import skipIf from django.utils.http import cookie_date, urlencode from django.utils.timezone import utc -class RequestsTests(unittest.TestCase): +class RequestsTests(SimpleTestCase): def test_httprequest(self): request = HttpRequest() self.assertEqual(list(request.GET.keys()), []) @@ -46,6 +50,44 @@ class RequestsTests(unittest.TestCase): self.assertEqual(request.META['REQUEST_METHOD'], 'bogus') self.assertEqual(request.META['SCRIPT_NAME'], '') + def test_wsgirequest_with_script_name(self): + """ + Ensure that the request's path is correctly assembled, regardless of + whether or not the SCRIPT_NAME has a trailing slash. + Refs #20169. + """ + # With trailing slash + request = WSGIRequest({'PATH_INFO': '/somepath/', 'SCRIPT_NAME': '/PREFIX/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) + self.assertEqual(request.path, '/PREFIX/somepath/') + # Without trailing slash + request = WSGIRequest({'PATH_INFO': '/somepath/', 'SCRIPT_NAME': '/PREFIX', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) + self.assertEqual(request.path, '/PREFIX/somepath/') + + def test_wsgirequest_with_force_script_name(self): + """ + Ensure that the FORCE_SCRIPT_NAME setting takes precedence over the + request's SCRIPT_NAME environment parameter. + Refs #20169. + """ + with override_settings(FORCE_SCRIPT_NAME='/FORCED_PREFIX/'): + request = WSGIRequest({'PATH_INFO': '/somepath/', 'SCRIPT_NAME': '/PREFIX/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) + self.assertEqual(request.path, '/FORCED_PREFIX/somepath/') + + def test_wsgirequest_path_with_force_script_name_trailing_slash(self): + """ + Ensure that the request's path is correctly assembled, regardless of + whether or not the FORCE_SCRIPT_NAME setting has a trailing slash. + Refs #20169. + """ + # With trailing slash + with override_settings(FORCE_SCRIPT_NAME='/FORCED_PREFIX/'): + request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) + self.assertEqual(request.path, '/FORCED_PREFIX/somepath/') + # Without trailing slash + with override_settings(FORCE_SCRIPT_NAME='/FORCED_PREFIX'): + request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) + self.assertEqual(request.path, '/FORCED_PREFIX/somepath/') + def test_wsgirequest_repr(self): request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) request.GET = {'get-key': 'get-value'} @@ -57,6 +99,16 @@ class RequestsTests(unittest.TestCase): self.assertEqual(build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}), str_prefix("<WSGIRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>")) + def test_wsgirequest_path_info(self): + def wsgi_str(path_info): + path_info = path_info.encode('utf-8') # Actual URL sent by the browser (bytestring) + if six.PY3: + path_info = path_info.decode('iso-8859-1') # Value in the WSGI environ dict (native string) + return path_info + # Regression for #19468 + request = WSGIRequest({'PATH_INFO': wsgi_str("/سلام/"), 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) + self.assertEqual(request.path, "/سلام/") + def test_parse_cookie(self): self.assertEqual(parse_cookie('invalid@key=true'), {}) @@ -70,7 +122,13 @@ class RequestsTests(unittest.TestCase): self.assertEqual(request.build_absolute_uri(location="/path/with:colons"), 'http://www.example.com/path/with:colons') - @override_settings(USE_X_FORWARDED_HOST=False) + @override_settings( + USE_X_FORWARDED_HOST=False, + ALLOWED_HOSTS=[ + 'forward.com', 'example.com', 'internal.com', '12.34.56.78', + '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com', + '.multitenant.com', 'INSENSITIVE.com', + ]) def test_http_get_host(self): # Check if X_FORWARDED_HOST is provided. request = HttpRequest() @@ -117,6 +175,9 @@ class RequestsTests(unittest.TestCase): '[2001:19f0:feee::dead:beef:cafe]', '[2001:19f0:feee::dead:beef:cafe]:8080', 'xn--4ca9at.com', # Punnycode for öäü.com + 'anything.multitenant.com', + 'multitenant.com', + 'insensitive.com', ] poisoned_hosts = [ @@ -125,6 +186,7 @@ class RequestsTests(unittest.TestCase): 'example.com:dr.frankenstein@evil.tld:80', 'example.com:80/badpath', 'example.com: recovermypassword.com', + 'other.com', # not in ALLOWED_HOSTS ] for host in legit_hosts: @@ -142,7 +204,7 @@ class RequestsTests(unittest.TestCase): } request.get_host() - @override_settings(USE_X_FORWARDED_HOST=True) + @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*']) def test_http_get_host_with_x_forwarded_host(self): # Check if X_FORWARDED_HOST is provided. request = HttpRequest() @@ -215,6 +277,66 @@ class RequestsTests(unittest.TestCase): request.get_host() + @override_settings(DEBUG=True, ALLOWED_HOSTS=[]) + def test_host_validation_disabled_in_debug_mode(self): + """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass.""" + request = HttpRequest() + request.META = { + 'HTTP_HOST': 'example.com', + } + self.assertEqual(request.get_host(), 'example.com') + + + @override_settings(ALLOWED_HOSTS=[]) + def test_get_host_suggestion_of_allowed_host(self): + """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS.""" + msg_invalid_host = "Invalid HTTP_HOST header: %r." + msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS." + + for host in [ # Valid-looking hosts + 'example.com', + '12.34.56.78', + '[2001:19f0:feee::dead:beef:cafe]', + 'xn--4ca9at.com', # Punnycode for öäü.com + ]: + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertRaisesMessage( + SuspiciousOperation, + msg_suggestion % (host, host), + request.get_host + ) + + for domain, port in [ # Valid-looking hosts with a port number + ('example.com', 80), + ('12.34.56.78', 443), + ('[2001:19f0:feee::dead:beef:cafe]', 8080), + ]: + host = '%s:%s' % (domain, port) + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertRaisesMessage( + SuspiciousOperation, + msg_suggestion % (host, domain), + request.get_host + ) + + for host in [ # Invalid hosts + 'example.com@evil.tld', + 'example.com:dr.frankenstein@evil.tld', + 'example.com:dr.frankenstein@evil.tld:80', + 'example.com:80/badpath', + 'example.com: recovermypassword.com', + ]: + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertRaisesMessage( + SuspiciousOperation, + msg_invalid_host % host, + request.get_host + ) + + def test_near_expiration(self): "Cookie will expire when an near expiration time is provided" response = HttpResponse() @@ -496,20 +618,6 @@ class RequestsTests(unittest.TestCase): self.assertEqual(request.read(13), b'--boundary\r\nC') self.assertEqual(request.POST, {'name': ['value']}) - def test_raw_post_data_returns_body(self): - """ - HttpRequest.raw_post_body should be the same as HttpRequest.body - """ - payload = FakePayload('Hello There!') - request = WSGIRequest({ - 'REQUEST_METHOD': 'POST', - 'CONTENT_LENGTH': len(payload), - 'wsgi.input': payload, - }) - - with warnings.catch_warnings(record=True): - self.assertEqual(request.body, request.raw_post_data) - def test_POST_connection_error(self): """ If wsgi.input.read() raises an exception while trying to read() the @@ -525,8 +633,59 @@ class RequestsTests(unittest.TestCase): 'CONTENT_LENGTH': len(payload), 'wsgi.input': ExplodingBytesIO(payload)}) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - with self.assertRaises(UnreadablePostError): - request.raw_post_data - self.assertEqual(len(w), 1) + with self.assertRaises(UnreadablePostError): + request.body + + +@skipIf(connection.vendor == 'sqlite' + and connection.settings_dict['NAME'] in ('', ':memory:'), + "Cannot establish two connections to an in-memory SQLite database.") +class DatabaseConnectionHandlingTests(TransactionTestCase): + + def setUp(self): + # Use a temporary connection to avoid messing with the main one. + self._old_default_connection = connections['default'] + del connections['default'] + + def tearDown(self): + try: + connections['default'].close() + finally: + connections['default'] = self._old_default_connection + + def test_request_finished_db_state(self): + # Force closing connection on request end + connection.settings_dict['CONN_MAX_AGE'] = 0 + + # The GET below will not succeed, but it will give a response with + # defined ._handler_class. That is needed for sending the + # request_finished signal. + response = self.client.get('/') + # Make sure there is an open connection + connection.cursor() + connection.enter_transaction_management() + signals.request_finished.send(sender=response._handler_class) + self.assertEqual(len(connection.transaction_state), 0) + + def test_request_finished_failed_connection(self): + # Force closing connection on request end + connection.settings_dict['CONN_MAX_AGE'] = 0 + + connection.enter_transaction_management() + connection.set_dirty() + # Test that the rollback doesn't succeed (for example network failure + # could cause this). + def fail_horribly(): + raise Exception("Horrible failure!") + connection._rollback = fail_horribly + try: + with self.assertRaises(Exception): + signals.request_finished.send(sender=self.__class__) + # The connection's state wasn't cleaned up + self.assertEqual(len(connection.transaction_state), 1) + finally: + del connection._rollback + # The connection will be cleaned on next request where the conn + # works again. + signals.request_finished.send(sender=self.__class__) + self.assertEqual(len(connection.transaction_state), 0) diff --git a/tests/regressiontests/model_forms_regress/__init__.py b/tests/reserved_names/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_forms_regress/__init__.py +++ b/tests/reserved_names/__init__.py diff --git a/tests/modeltests/reserved_names/models.py b/tests/reserved_names/models.py index 8e942b20e8..8e942b20e8 100644 --- a/tests/modeltests/reserved_names/models.py +++ b/tests/reserved_names/models.py diff --git a/tests/modeltests/reserved_names/tests.py b/tests/reserved_names/tests.py index 87f7a42ec4..ddffe08d34 100644 --- a/tests/modeltests/reserved_names/tests.py +++ b/tests/reserved_names/tests.py @@ -42,8 +42,8 @@ class ReservedNameTests(TestCase): self.generate() resp = Thing.objects.dates('where', 'year') self.assertEqual(list(resp), [ - datetime.datetime(2005, 1, 1, 0, 0), - datetime.datetime(2006, 1, 1, 0, 0), + datetime.date(2005, 1, 1), + datetime.date(2006, 1, 1), ]) def test_month_filter(self): diff --git a/tests/regressiontests/model_formsets_regress/__init__.py b/tests/resolve_url/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_formsets_regress/__init__.py +++ b/tests/resolve_url/__init__.py diff --git a/tests/regressiontests/resolve_url/models.py b/tests/resolve_url/models.py index 238902edd2..238902edd2 100644 --- a/tests/regressiontests/resolve_url/models.py +++ b/tests/resolve_url/models.py diff --git a/tests/regressiontests/resolve_url/tests.py b/tests/resolve_url/tests.py index d0bf44abde..d0bf44abde 100644 --- a/tests/regressiontests/resolve_url/tests.py +++ b/tests/resolve_url/tests.py diff --git a/tests/regressiontests/model_inheritance_regress/__init__.py b/tests/reverse_lookup/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_inheritance_regress/__init__.py +++ b/tests/reverse_lookup/__init__.py diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/reverse_lookup/models.py index ed58177770..ed58177770 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/reverse_lookup/models.py diff --git a/tests/modeltests/reverse_lookup/tests.py b/tests/reverse_lookup/tests.py index 549ee66392..549ee66392 100644 --- a/tests/modeltests/reverse_lookup/tests.py +++ b/tests/reverse_lookup/tests.py diff --git a/tests/regressiontests/model_inheritance_select_related/__init__.py b/tests/reverse_single_related/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_inheritance_select_related/__init__.py +++ b/tests/reverse_single_related/__init__.py diff --git a/tests/regressiontests/reverse_single_related/models.py b/tests/reverse_single_related/models.py index 898be8411b..30ba345120 100644 --- a/tests/regressiontests/reverse_single_related/models.py +++ b/tests/reverse_single_related/models.py @@ -2,8 +2,8 @@ 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) + def get_queryset(self): + return super(SourceManager, self).get_queryset().filter(is_public=True) class Source(models.Model): is_public = models.BooleanField() diff --git a/tests/regressiontests/reverse_single_related/tests.py b/tests/reverse_single_related/tests.py index 0c755c4db6..0c755c4db6 100644 --- a/tests/regressiontests/reverse_single_related/tests.py +++ b/tests/reverse_single_related/tests.py diff --git a/tests/runtests.py b/tests/runtests.py index 8c56e273b5..ee2488c4a1 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,35 +1,28 @@ #!/usr/bin/env python +import logging import os import shutil import subprocess import sys import tempfile -import warnings from django import contrib from django.utils._os import upath from django.utils import six -# databrowse is deprecated, but we still want to run its tests -warnings.filterwarnings('ignore', "The Databrowse contrib app is deprecated", - DeprecationWarning, 'django.contrib.databrowse') - CONTRIB_DIR_NAME = 'django.contrib' -MODEL_TESTS_DIR_NAME = 'modeltests' -REGRESSION_TESTS_DIR_NAME = 'regressiontests' TEST_TEMPLATE_DIR = 'templates' -RUNTESTS_DIR = os.path.dirname(upath(__file__)) +RUNTESTS_DIR = os.path.abspath(os.path.dirname(upath(__file__))) CONTRIB_DIR = os.path.dirname(upath(contrib.__file__)) -MODEL_TEST_DIR = os.path.join(RUNTESTS_DIR, MODEL_TESTS_DIR_NAME) -REGRESSION_TEST_DIR = os.path.join(RUNTESTS_DIR, REGRESSION_TESTS_DIR_NAME) TEMP_DIR = tempfile.mkdtemp(prefix='django_') os.environ['DJANGO_TEST_TEMP_DIR'] = TEMP_DIR -REGRESSION_SUBDIRS_TO_SKIP = [] +SUBDIRS_TO_SKIP = ['templates'] ALWAYS_INSTALLED_APPS = [ + 'shared_models', 'django.contrib.contenttypes', 'django.contrib.auth', 'django.contrib.sites', @@ -40,12 +33,11 @@ ALWAYS_INSTALLED_APPS = [ 'django.contrib.comments', 'django.contrib.admin', 'django.contrib.admindocs', - 'django.contrib.databrowse', 'django.contrib.staticfiles', 'django.contrib.humanize', - 'regressiontests.staticfiles_tests', - 'regressiontests.staticfiles_tests.apps.test', - 'regressiontests.staticfiles_tests.apps.no_label', + 'staticfiles_tests', + 'staticfiles_tests.apps.test', + 'staticfiles_tests.apps.no_label', ] def geodjango(settings): @@ -57,30 +49,27 @@ def geodjango(settings): def get_test_modules(): modules = [] for loc, dirpath in ( - (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), - (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR), + (None, RUNTESTS_DIR), (CONTRIB_DIR_NAME, CONTRIB_DIR)): for f in os.listdir(dirpath): - if (f.startswith('__init__') or - f.startswith('.') or + if ('.' in f or # Python 3 byte code dirs (PEP 3147) f == '__pycache__' or f.startswith('sql') or - os.path.basename(f) in REGRESSION_SUBDIRS_TO_SKIP): + os.path.basename(f) in SUBDIRS_TO_SKIP or + os.path.isfile(f)): continue modules.append((loc, f)) return modules def setup(verbosity, test_labels): from django.conf import settings + from django.db.models.loading import get_apps, load_app state = { 'INSTALLED_APPS': settings.INSTALLED_APPS, 'ROOT_URLCONF': getattr(settings, "ROOT_URLCONF", ""), 'TEMPLATE_DIRS': settings.TEMPLATE_DIRS, - 'USE_I18N': settings.USE_I18N, - 'LOGIN_URL': settings.LOGIN_URL, 'LANGUAGE_CODE': settings.LANGUAGE_CODE, - 'MIDDLEWARE_CLASSES': settings.MIDDLEWARE_CLASSES, 'STATIC_URL': settings.STATIC_URL, 'STATIC_ROOT': settings.STATIC_ROOT, } @@ -91,25 +80,18 @@ def setup(verbosity, test_labels): settings.STATIC_URL = '/static/' settings.STATIC_ROOT = os.path.join(TEMP_DIR, 'static') settings.TEMPLATE_DIRS = (os.path.join(RUNTESTS_DIR, TEST_TEMPLATE_DIR),) - settings.USE_I18N = True settings.LANGUAGE_CODE = 'en' - settings.LOGIN_URL = 'django.contrib.auth.views.login' - settings.MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.common.CommonMiddleware', - ) settings.SITE_ID = 1 - # For testing comment-utils, we require the MANAGERS attribute - # to be set, so that a test email is sent out which we catch - # in our tests. - settings.MANAGERS = ("admin@djangoproject.com",) + + if verbosity > 0: + # Ensure any warnings captured to logging are piped through a verbose + # logging handler. If any -W options were passed explicitly on command + # line, warnings are not captured, and this has no effect. + logger = logging.getLogger('py.warnings') + handler = logging.StreamHandler() + logger.addHandler(handler) # Load all the ALWAYS_INSTALLED_APPS. - # (This import statement is intentionally delayed until after we - # access settings because of the USE_I18N dependency.) - from django.db.models.loading import get_apps, load_app get_apps() # Load all the test model apps. @@ -124,7 +106,10 @@ def setup(verbosity, test_labels): settings.INSTALLED_APPS.extend(['django.contrib.gis', 'django.contrib.sitemaps']) for module_dir, module_name in test_modules: - module_label = '.'.join([module_dir, module_name]) + if module_dir: + module_label = '.'.join([module_dir, module_name]) + else: + module_label = module_name # if the module was named on the command line, or # no modules were named (i.e., run all), import # this module and add it to the list to test. @@ -306,7 +291,11 @@ if __name__ == "__main__": '--liveserver', action='store', dest='liveserver', default=None, help='Overrides the default address where the live server (used with ' 'LiveServerTestCase) is expected to run from. The default value ' - 'is localhost:8081.'), + 'is localhost:8081.') + parser.add_option( + '--selenium', action='store_true', dest='selenium', + default=False, + help='Run the Selenium tests as well (if Selenium is installed)') options, args = parser.parse_args() if options.settings: os.environ['DJANGO_SETTINGS_MODULE'] = options.settings @@ -319,6 +308,9 @@ if __name__ == "__main__": if options.liveserver is not None: os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options.liveserver + if options.selenium: + os.environ['DJANGO_SELENIUM_TESTS'] = '1' + if options.bisect: bisect_tests(options.bisect, options, args) elif options.pair: diff --git a/tests/regressiontests/model_permalink/__init__.py b/tests/save_delete_hooks/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_permalink/__init__.py +++ b/tests/save_delete_hooks/__init__.py diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/save_delete_hooks/models.py index a6e1abfb77..a6e1abfb77 100644 --- a/tests/modeltests/save_delete_hooks/models.py +++ b/tests/save_delete_hooks/models.py diff --git a/tests/modeltests/save_delete_hooks/tests.py b/tests/save_delete_hooks/tests.py index 42e0d4a80e..42e0d4a80e 100644 --- a/tests/modeltests/save_delete_hooks/tests.py +++ b/tests/save_delete_hooks/tests.py diff --git a/tests/regressiontests/model_regress/__init__.py b/tests/schema/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/model_regress/__init__.py +++ b/tests/schema/__init__.py diff --git a/tests/modeltests/schema/models.py b/tests/schema/models.py index fdf950860c..fdf950860c 100644 --- a/tests/modeltests/schema/models.py +++ b/tests/schema/models.py diff --git a/tests/modeltests/schema/tests.py b/tests/schema/tests.py index 6ac72b4c02..72c0c07af5 100644 --- a/tests/modeltests/schema/tests.py +++ b/tests/schema/tests.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import copy import datetime -from django.test import TestCase +from django.test import TransactionTestCase from django.utils.unittest import skipUnless from django.db import connection, DatabaseError, IntegrityError from django.db.models.fields import IntegerField, TextField, CharField, SlugField @@ -10,7 +10,7 @@ from django.db.models.loading import cache, default_cache, AppCache from .models import Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagUniqueRename, UniqueTest -class SchemaTests(TestCase): +class SchemaTests(TransactionTestCase): """ Tests that the schema-alteration code works correctly. @@ -26,9 +26,7 @@ class SchemaTests(TestCase): def setUp(self): # Make sure we're in manual transaction mode - connection.commit_unless_managed() - connection.enter_transaction_management() - connection.managed(True) + connection.set_autocommit(False) # The unmanaged models need to be removed after the test in order to # prevent bad interactions with the flush operation in other tests. self.app_cache = AppCache() @@ -40,10 +38,11 @@ class SchemaTests(TestCase): def tearDown(self): # Delete any tables made for our models + connection.rollback() self.delete_tables() # Rollback anything that may have happened connection.rollback() - connection.leave_transaction_management() + connection.set_autocommit(True) cache.set_cache(default_cache) cache.app_models['schema'] = {} # One M2M gets left in the old cache diff --git a/tests/regressiontests/admin_custom_urls/__init__.py b/tests/select_for_update/__init__.py index 792d600548..792d600548 100644 --- a/tests/regressiontests/admin_custom_urls/__init__.py +++ b/tests/select_for_update/__init__.py diff --git a/tests/modeltests/select_for_update/models.py b/tests/select_for_update/models.py index 48ad58faa9..48ad58faa9 100644 --- a/tests/modeltests/select_for_update/models.py +++ b/tests/select_for_update/models.py diff --git a/tests/modeltests/select_for_update/tests.py b/tests/select_for_update/tests.py index e3e4d9e7e2..c2fa22705a 100644 --- a/tests/modeltests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -24,8 +24,7 @@ requires_threading = unittest.skipUnless(threading, 'requires threading') class SelectForUpdateTests(TransactionTestCase): def setUp(self): - transaction.enter_transaction_management(True) - transaction.managed(True) + transaction.enter_transaction_management() self.person = Person.objects.create(name='Reinhardt') # We have to commit here so that code in run_select_for_update can @@ -37,7 +36,6 @@ class SelectForUpdateTests(TransactionTestCase): new_connections = ConnectionHandler(settings.DATABASES) self.new_connection = new_connections[DEFAULT_DB_ALIAS] self.new_connection.enter_transaction_management() - self.new_connection.managed(True) # We need to set settings.DEBUG to True so we can capture # the output SQL to examine. @@ -48,9 +46,8 @@ class SelectForUpdateTests(TransactionTestCase): try: # We don't really care if this fails - some of the tests will set # this in the course of their run. - transaction.managed(False) - transaction.leave_transaction_management() - self.new_connection.leave_transaction_management() + transaction.abort() + self.new_connection.abort() except transaction.TransactionManagementError: pass self.new_connection.close() @@ -73,7 +70,7 @@ class SelectForUpdateTests(TransactionTestCase): def end_blocking_transaction(self): # Roll back the blocking transaction. - self.new_connection._rollback() + self.new_connection.rollback() def has_for_update_sql(self, tested_connection, nowait=False): # Examine the SQL that was executed to determine whether it @@ -82,9 +79,6 @@ class SelectForUpdateTests(TransactionTestCase): sql = tested_connection.queries[-1]['sql'] return bool(sql.find(for_update_sql) > -1) - def check_exc(self, exc): - self.assertTrue(isinstance(exc, DatabaseError)) - @skipUnlessDBFeature('has_select_for_update') def test_for_update_sql_generated(self): """ @@ -119,6 +113,7 @@ class SelectForUpdateTests(TransactionTestCase): """ self.start_blocking_transaction() status = [] + thread = threading.Thread( target=self.run_select_for_update, args=(status,), @@ -129,7 +124,7 @@ class SelectForUpdateTests(TransactionTestCase): time.sleep(1) thread.join() self.end_blocking_transaction() - self.check_exc(status[-1]) + self.assertIsInstance(status[-1], DatabaseError) # In Python 2.6 beta and some final releases, exceptions raised in __len__ # are swallowed (Python issue 1242657), so these cases return an empty @@ -164,8 +159,7 @@ class SelectForUpdateTests(TransactionTestCase): try: # We need to enter transaction management again, as this is done on # per-thread basis - transaction.enter_transaction_management(True) - transaction.managed(True) + transaction.enter_transaction_management() people = list( Person.objects.all().select_for_update(nowait=nowait) ) @@ -177,6 +171,7 @@ class SelectForUpdateTests(TransactionTestCase): finally: # This method is run in a separate thread. It uses its own # database connection. Close it without waiting for the GC. + transaction.abort() connection.close() @requires_threading @@ -260,7 +255,7 @@ class SelectForUpdateTests(TransactionTestCase): time.sleep(1) thread.join() self.end_blocking_transaction() - self.check_exc(status[-1]) + self.assertIsInstance(status[-1], DatabaseError) @skipUnlessDBFeature('has_select_for_update') def test_transaction_dirty_managed(self): @@ -271,13 +266,3 @@ class SelectForUpdateTests(TransactionTestCase): """ people = list(Person.objects.select_for_update()) self.assertTrue(transaction.is_dirty()) - - @skipUnlessDBFeature('has_select_for_update') - def test_transaction_not_dirty_unmanaged(self): - """ If we're not under txn management, the txn will never be - marked as dirty. - """ - transaction.managed(False) - transaction.leave_transaction_management() - people = list(Person.objects.select_for_update()) - self.assertFalse(transaction.is_dirty()) diff --git a/tests/regressiontests/modeladmin/__init__.py b/tests/select_related/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/modeladmin/__init__.py +++ b/tests/select_related/__init__.py diff --git a/tests/modeltests/select_related/models.py b/tests/select_related/models.py index ec41957adf..ec41957adf 100644 --- a/tests/modeltests/select_related/models.py +++ b/tests/select_related/models.py diff --git a/tests/modeltests/select_related/tests.py b/tests/select_related/tests.py index 557a5c318a..27d65fecb1 100644 --- a/tests/modeltests/select_related/tests.py +++ b/tests/select_related/tests.py @@ -1,5 +1,7 @@ from __future__ import absolute_import, unicode_literals +import warnings + from django.test import TestCase from .models import Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species @@ -53,7 +55,9 @@ class SelectRelatedTests(TestCase): extra queries """ with self.assertNumQueries(1): - person = Species.objects.select_related(depth=10).get(name="sapiens") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + person = Species.objects.select_related(depth=10).get(name="sapiens") domain = person.genus.family.order.klass.phylum.kingdom.domain self.assertEqual(domain.name, 'Eukaryota') @@ -94,7 +98,9 @@ class SelectRelatedTests(TestCase): """ # Notice: one fewer queries than above because of depth=1 with self.assertNumQueries(expected): - pea = Species.objects.select_related(depth=depth).get(name="sativum") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + pea = Species.objects.select_related(depth=depth).get(name="sativum") self.assertEqual( pea.genus.family.order.klass.phylum.kingdom.domain.name, 'Eukaryota' @@ -113,14 +119,18 @@ class SelectRelatedTests(TestCase): particular level. This can be used on lists as well. """ with self.assertNumQueries(5): - world = Species.objects.all().select_related(depth=2) - orders = [o.genus.family.order.name for o in world] + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + world = Species.objects.all().select_related(depth=2) + orders = [o.genus.family.order.name for o in world] self.assertEqual(sorted(orders), ['Agaricales', 'Diptera', 'Fabales', 'Primates']) def test_select_related_with_extra(self): - s = Species.objects.all().select_related(depth=1)\ - .extra(select={'a': 'select_related_species.id + 10'})[0] + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + s = Species.objects.all().select_related(depth=1)\ + .extra(select={'a': 'select_related_species.id + 10'})[0] self.assertEqual(s.id + 10, s.a) def test_certain_fields(self): @@ -156,7 +166,9 @@ class SelectRelatedTests(TestCase): self.assertEqual(s, 'Diptera') def test_depth_fields_fails(self): - self.assertRaises(TypeError, - Species.objects.select_related, - 'genus__family__order', depth=4 - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + self.assertRaises(TypeError, + Species.objects.select_related, + 'genus__family__order', depth=4 + ) diff --git a/tests/regressiontests/multiple_database/__init__.py b/tests/select_related_onetoone/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/multiple_database/__init__.py +++ b/tests/select_related_onetoone/__init__.py diff --git a/tests/regressiontests/select_related_onetoone/models.py b/tests/select_related_onetoone/models.py index d32faafbb9..d32faafbb9 100644 --- a/tests/regressiontests/select_related_onetoone/models.py +++ b/tests/select_related_onetoone/models.py diff --git a/tests/regressiontests/select_related_onetoone/tests.py b/tests/select_related_onetoone/tests.py index d4a1275e49..fce8fc4e73 100644 --- a/tests/regressiontests/select_related_onetoone/tests.py +++ b/tests/select_related_onetoone/tests.py @@ -183,11 +183,11 @@ class ReverseSelectRelatedTestCase(TestCase): p = Parent2.objects.select_related('child1').only( 'id2', 'child1__value').get(name2="n2") with self.assertNumQueries(1): - self.assertEquals(p.name2, 'n2') + self.assertEqual(p.name2, 'n2') p = Parent2.objects.select_related('child1').only( 'id2', 'child1__value').get(name2="n2") with self.assertNumQueries(1): - self.assertEquals(p.child1.name2, 'n2') + self.assertEqual(p.child1.name2, 'n2') @unittest.expectedFailure def test_inheritance_deferred2(self): @@ -202,9 +202,9 @@ class ReverseSelectRelatedTestCase(TestCase): self.assertEqual(p.child1.child4.id2, c.id2) p = qs.get(name2="n2") with self.assertNumQueries(1): - self.assertEquals(p.child1.name2, 'n2') + self.assertEqual(p.child1.name2, 'n2') p = qs.get(name2="n2") with self.assertNumQueries(1): - self.assertEquals(p.child1.name1, 'n1') + self.assertEqual(p.child1.name1, 'n1') with self.assertNumQueries(1): - self.assertEquals(p.child1.child4.name1, 'n1') + self.assertEqual(p.child1.child4.name1, 'n1') diff --git a/tests/regressiontests/nested_foreign_keys/__init__.py b/tests/select_related_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/nested_foreign_keys/__init__.py +++ b/tests/select_related_regress/__init__.py diff --git a/tests/regressiontests/select_related_regress/models.py b/tests/select_related_regress/models.py index a291a547f7..620b8eb5ea 100644 --- a/tests/regressiontests/select_related_regress/models.py +++ b/tests/select_related_regress/models.py @@ -94,3 +94,17 @@ class Item(models.Model): def __str__(self): return self.name + +# Models for testing bug #19870. +@python_2_unicode_compatible +class Fowl(models.Model): + name = models.CharField(max_length=10) + + def __str__(self): + return self.name + +class Hen(Fowl): + pass + +class Chick(Fowl): + mother = models.ForeignKey(Hen) diff --git a/tests/regressiontests/select_related_regress/tests.py b/tests/select_related_regress/tests.py index 7f93a1c33c..f6d21b2dd9 100644 --- a/tests/regressiontests/select_related_regress/tests.py +++ b/tests/select_related_regress/tests.py @@ -5,7 +5,7 @@ from django.utils import six from .models import (Building, Child, Device, Port, Item, Country, Connection, ClientStatus, State, Client, SpecialClient, TUser, Person, Student, - Organizer, Class, Enrollment) + Organizer, Class, Enrollment, Hen, Chick) class SelectRelatedRegressTests(TestCase): @@ -139,3 +139,37 @@ class SelectRelatedRegressTests(TestCase): self.assertEqual(troy.name, 'Troy Buswell') self.assertEqual(troy.value, 42) self.assertEqual(troy.state.name, 'Western Australia') + + def test_null_join_promotion(self): + australia = Country.objects.create(name='Australia') + active = ClientStatus.objects.create(name='active') + + wa = State.objects.create(name="Western Australia", country=australia) + bob = Client.objects.create(name='Bob', status=active) + jack = Client.objects.create(name='Jack', status=active, state=wa) + qs = Client.objects.filter(state=wa).select_related('state') + with self.assertNumQueries(1): + self.assertEqual(list(qs), [jack]) + self.assertEqual(qs[0].state, wa) + # The select_related join wasn't promoted as there was already an + # existing (even if trimmed) inner join to state. + self.assertFalse('LEFT OUTER' in str(qs.query)) + qs = Client.objects.select_related('state').order_by('name') + with self.assertNumQueries(1): + self.assertEqual(list(qs), [bob, jack]) + self.assertIs(qs[0].state, None) + self.assertEqual(qs[1].state, wa) + # The select_related join was promoted as there is already an + # existing join. + self.assertTrue('LEFT OUTER' in str(qs.query)) + + def test_regression_19870(self): + """ + Regression for #19870 + + """ + hen = Hen.objects.create(name='Hen') + chick = Chick.objects.create(name='Chick', mother=hen) + + self.assertEqual(Chick.objects.all()[0].mother.name, 'Hen') + self.assertEqual(Chick.objects.select_related()[0].mother.name, 'Hen') diff --git a/tests/regressiontests/null_fk/__init__.py b/tests/serializers/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/null_fk/__init__.py +++ b/tests/serializers/__init__.py diff --git a/tests/modeltests/serializers/models.py b/tests/serializers/models.py index 569f2cec49..569f2cec49 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/serializers/models.py diff --git a/tests/modeltests/serializers/tests.py b/tests/serializers/tests.py index e39f17b68a..a96a1af748 100644 --- a/tests/modeltests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -255,7 +255,7 @@ class SerializersTestBase(object): for obj in deserial_objs: self.assertFalse(obj.object.id) obj.save() - self.assertEqual(Category.objects.all().count(), 4) + self.assertEqual(Category.objects.all().count(), 5) class SerializersTransactionTestBase(object): @@ -268,7 +268,6 @@ class SerializersTransactionTestBase(object): # within a transaction in order to test forward reference # handling. transaction.enter_transaction_management() - transaction.managed(True) objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str) with connection.constraint_checks_disabled(): for obj in objs: @@ -290,6 +289,9 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase): <object model="serializers.category"> <field type="CharField" name="name">Reference</field> </object> + <object model="serializers.category"> + <field type="CharField" name="name">Non-fiction</field> + </object> </django-objects>""" @staticmethod @@ -351,7 +353,15 @@ class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, Transacti class JsonSerializerTestCase(SerializersTestBase, TestCase): serializer_name = "json" - pkless_str = """[{"pk": null, "model": "serializers.category", "fields": {"name": "Reference"}}]""" + pkless_str = """[ + { + "pk": null, + "model": "serializers.category", + "fields": {"name": "Reference"} + }, { + "model": "serializers.category", + "fields": {"name": "Non-fiction"} + }]""" @staticmethod def _validate_output(serial_str): @@ -433,6 +443,9 @@ else: pkless_str = """- fields: name: Reference pk: null + model: serializers.category +- fields: + name: Non-fiction model: serializers.category""" @staticmethod diff --git a/tests/regressiontests/null_fk_ordering/__init__.py b/tests/serializers_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/null_fk_ordering/__init__.py +++ b/tests/serializers_regress/__init__.py diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/serializers_regress/models.py index af261f9cbc..21fe073122 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/serializers_regress/models.py @@ -12,6 +12,9 @@ from django.contrib.contenttypes.models import ContentType # The following classes are for testing basic data # marshalling, including NULL values, where allowed. +class BinaryData(models.Model): + data = models.BinaryField(null=True) + class BooleanData(models.Model): data = models.BooleanField() diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index 2583a1ea04..72e3825d91 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -10,6 +10,7 @@ from __future__ import absolute_import, unicode_literals import datetime import decimal +from django.core.serializers.xml_serializer import DTDForbidden try: import yaml @@ -23,10 +24,11 @@ from django.db import connection, models from django.http import HttpResponse from django.test import TestCase from django.utils import six +from django.utils.encoding import force_text from django.utils.functional import curry from django.utils.unittest import skipUnless -from .models import (BooleanData, CharData, DateData, DateTimeData, EmailData, +from .models import (BinaryData, BooleanData, CharData, DateData, DateTimeData, EmailData, FileData, FilePathData, DecimalData, FloatData, IntegerData, IPAddressData, GenericIPAddressData, NullBooleanData, PositiveIntegerData, PositiveSmallIntegerData, SlugData, SmallData, TextData, TimeData, @@ -115,10 +117,17 @@ def inherited_create(pk, klass, data): # 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)) - ) + if klass == BinaryData and data is not None: + testcase.assertEqual(bytes(data), bytes(instance.data), + "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % ( + pk, repr(bytes(data)), type(data), repr(bytes(instance.data)), + type(instance.data)) + ) + else: + testcase.assertEqual(data, instance.data, + "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % ( + pk, data, type(data), instance, type(instance.data)) + ) def generic_compare(testcase, pk, klass, data): instance = klass.objects.get(id=pk) @@ -174,8 +183,10 @@ 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, 1, BinaryData, six.memoryview(b"\x05\xFD\x00")), + (data_obj, 2, BinaryData, None), + (data_obj, 5, BooleanData, True), + (data_obj, 6, BooleanData, False), (data_obj, 10, CharData, "Test Char Data"), (data_obj, 11, CharData, ""), (data_obj, 12, CharData, "None"), @@ -506,7 +517,6 @@ def streamTest(format, self): self.assertEqual(string_data, stream.getvalue()) else: self.assertEqual(string_data, stream.content.decode('utf-8')) - stream.close() for format in serializers.get_serializer_formats(): setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) @@ -515,3 +525,17 @@ for format in serializers.get_serializer_formats(): if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) + +class XmlDeserializerSecurityTests(TestCase): + + def test_no_dtd(self): + """ + The XML deserializer shouldn't allow a DTD. + + This is the most straightforward way to prevent all entity definitions + and avoid both external entities and entity-expansion attacks. + + """ + xml = '<?xml version="1.0" standalone="no"?><!DOCTYPE example SYSTEM "http://example.com/example.dtd">' + with self.assertRaises(DTDForbidden): + next(serializers.deserialize('xml', xml)) diff --git a/tests/regressiontests/null_queries/__init__.py b/tests/servers/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/null_queries/__init__.py +++ b/tests/servers/__init__.py diff --git a/tests/regressiontests/servers/fixtures/testdata.json b/tests/servers/fixtures/testdata.json index d81b2253d2..d81b2253d2 100644 --- a/tests/regressiontests/servers/fixtures/testdata.json +++ b/tests/servers/fixtures/testdata.json diff --git a/tests/regressiontests/servers/media/example_media_file.txt b/tests/servers/media/example_media_file.txt index dd2dda94e3..dd2dda94e3 100644 --- a/tests/regressiontests/servers/media/example_media_file.txt +++ b/tests/servers/media/example_media_file.txt diff --git a/tests/regressiontests/servers/models.py b/tests/servers/models.py index 6e1414ae19..6e1414ae19 100644 --- a/tests/regressiontests/servers/models.py +++ b/tests/servers/models.py diff --git a/tests/regressiontests/servers/static/example_static_file.txt b/tests/servers/static/example_static_file.txt index b5d8342212..b5d8342212 100644 --- a/tests/regressiontests/servers/static/example_static_file.txt +++ b/tests/servers/static/example_static_file.txt diff --git a/tests/regressiontests/servers/tests.py b/tests/servers/tests.py index 1a7552ed11..920149477c 100644 --- a/tests/regressiontests/servers/tests.py +++ b/tests/servers/tests.py @@ -30,7 +30,7 @@ TEST_SETTINGS = { class LiveServerBase(LiveServerTestCase): - urls = 'regressiontests.servers.urls' + urls = 'servers.urls' fixtures = ['testdata.json'] @classmethod @@ -96,6 +96,8 @@ class LiveServerAddress(LiveServerBase): raise Exception("The line above should have raised an exception") except exception: pass + finally: + super(LiveServerAddress, cls).tearDownClass() def test_test_test(self): # Intentionally empty method so that the test is picked up by the diff --git a/tests/regressiontests/servers/urls.py b/tests/servers/urls.py index a857c45f95..a857c45f95 100644 --- a/tests/regressiontests/servers/urls.py +++ b/tests/servers/urls.py diff --git a/tests/regressiontests/servers/views.py b/tests/servers/views.py index 00baf4b5ac..00baf4b5ac 100644 --- a/tests/regressiontests/servers/views.py +++ b/tests/servers/views.py diff --git a/tests/regressiontests/one_to_one_regress/__init__.py b/tests/settings_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/one_to_one_regress/__init__.py +++ b/tests/settings_tests/__init__.py diff --git a/tests/regressiontests/settings_tests/models.py b/tests/settings_tests/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/settings_tests/models.py +++ b/tests/settings_tests/models.py diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/settings_tests/tests.py index 68f620b5c9..97e43ff0eb 100644 --- a/tests/regressiontests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -287,67 +287,3 @@ class SecureProxySslHeaderTest(TestCase): req = HttpRequest() req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'https' self.assertEqual(req.is_secure(), True) - -class EnvironmentVariableTest(TestCase): - """ - Ensures proper settings file is used in setup_environ if - DJANGO_SETTINGS_MODULE is set in the environment. - """ - # Decide what to do with these tests when setup_environ() gets removed in Django 1.6 - def setUp(self): - self.original_value = os.environ.get('DJANGO_SETTINGS_MODULE') - self.save_warnings_state() - warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.core.management') - - def tearDown(self): - self.restore_warnings_state() - if self.original_value: - os.environ['DJANGO_SETTINGS_MODULE'] = self.original_value - elif 'DJANGO_SETTINGS_MODULE' in os.environ: - del(os.environ['DJANGO_SETTINGS_MODULE']) - - def test_env_var_used(self): - """ - If the environment variable is set, do not ignore it. However, the - kwarg original_settings_path takes precedence. - - This tests both plus the default (neither set). - """ - from django.core.management import setup_environ - - # whatever was already there - original_module = os.environ.get( - 'DJANGO_SETTINGS_MODULE', - 'the default' - ) - - # environment variable set by user - user_override = 'custom.settings' - - # optional argument to setup_environ - orig_path = 'original.path' - - # expect default - setup_environ(global_settings) - self.assertEqual( - os.environ.get('DJANGO_SETTINGS_MODULE'), - original_module - ) - - # override with environment variable - os.environ['DJANGO_SETTINGS_MODULE'] = user_override - setup_environ(global_settings) - - self.assertEqual( - os.environ.get('DJANGO_SETTINGS_MODULE'), - user_override - ) - - # pass in original_settings_path (should take precedence) - os.environ['DJANGO_SETTINGS_MODULE'] = user_override - setup_environ(global_settings, original_settings_path = orig_path) - - self.assertEqual( - os.environ.get('DJANGO_SETTINGS_MODULE'), - orig_path - ) diff --git a/tests/regressiontests/pagination/__init__.py b/tests/shared_models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/pagination/__init__.py +++ b/tests/shared_models/__init__.py diff --git a/tests/shared_models/models.py b/tests/shared_models/models.py new file mode 100644 index 0000000000..145edad1bf --- /dev/null +++ b/tests/shared_models/models.py @@ -0,0 +1,37 @@ +from __future__ import unicode_literals + +from django.db import models +from django.utils import timezone +from django.utils.encoding import python_2_unicode_compatible + + +class Tag(models.Model): + name = models.CharField(max_length=255) + + +@python_2_unicode_compatible +class Author(models.Model): + name = models.CharField(max_length=100, help_text='Use both first and last names.', + unique=True) + + class Meta: + ordering = ['name'] + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class Book(models.Model): + title = models.CharField(max_length=200) + pages = models.IntegerField(default=0) + author = models.ForeignKey(Author, null=True, blank=True) + pubdate = models.DateTimeField() + tags = models.ManyToManyField(Tag) + + class Meta: + ordering = ['-pubdate', 'title'] + unique_together = ['title', 'author'] + + def __str__(self): + return self.title diff --git a/tests/regressiontests/queries/__init__.py b/tests/signals/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/queries/__init__.py +++ b/tests/signals/__init__.py diff --git a/tests/modeltests/signals/models.py b/tests/signals/models.py index e54a22fce9..e54a22fce9 100644 --- a/tests/modeltests/signals/models.py +++ b/tests/signals/models.py diff --git a/tests/modeltests/signals/tests.py b/tests/signals/tests.py index 58f25c2868..58f25c2868 100644 --- a/tests/modeltests/signals/tests.py +++ b/tests/signals/tests.py diff --git a/tests/regressiontests/queryset_pickle/__init__.py b/tests/signals_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/queryset_pickle/__init__.py +++ b/tests/signals_regress/__init__.py diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/models.py b/tests/signals_regress/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/apps/no_label/models.py +++ b/tests/signals_regress/models.py diff --git a/tests/regressiontests/signals_regress/tests.py b/tests/signals_regress/tests.py index 8fb3ad5a48..b806d2116e 100644 --- a/tests/regressiontests/signals_regress/tests.py +++ b/tests/signals_regress/tests.py @@ -3,7 +3,7 @@ from __future__ import absolute_import from django.db import models from django.test import TestCase -from .models import Author, Book +from shared_models.models import Author, Book class SignalsRegressTests(TestCase): @@ -77,7 +77,7 @@ class SignalsRegressTests(TestCase): "Is created" ]) - b1 = Book(name='Snow Crash') + b1 = Book(title='Snow Crash', pubdate='2012-02-02 12:00') self.assertEqual(self.get_signal_output(b1.save), [ "pre_save signal, Snow Crash", "post_save signal, Snow Crash", @@ -87,7 +87,7 @@ class SignalsRegressTests(TestCase): def test_m2m_signals(self): """ Assigning and removing to/from m2m shouldn't generate an m2m signal """ - b1 = Book(name='Snow Crash') + b1 = Book(title='Snow Crash', pubdate='2012-02-02 12:00') self.get_signal_output(b1.save) a1 = Author(name='Neal Stephenson') self.get_signal_output(a1.save) diff --git a/tests/regressiontests/resolve_url/__init__.py b/tests/signed_cookies_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/resolve_url/__init__.py +++ b/tests/signed_cookies_tests/__init__.py diff --git a/tests/regressiontests/middleware/models.py b/tests/signed_cookies_tests/models.py index 71abcc5198..71abcc5198 100644 --- a/tests/regressiontests/middleware/models.py +++ b/tests/signed_cookies_tests/models.py diff --git a/tests/regressiontests/signed_cookies_tests/tests.py b/tests/signed_cookies_tests/tests.py index 800092a2d7..800092a2d7 100644 --- a/tests/regressiontests/signed_cookies_tests/tests.py +++ b/tests/signed_cookies_tests/tests.py diff --git a/tests/regressiontests/reverse_single_related/__init__.py b/tests/signing/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/reverse_single_related/__init__.py +++ b/tests/signing/__init__.py diff --git a/tests/regressiontests/signed_cookies_tests/models.py b/tests/signing/models.py index 71abcc5198..71abcc5198 100644 --- a/tests/regressiontests/signed_cookies_tests/models.py +++ b/tests/signing/models.py diff --git a/tests/regressiontests/signing/tests.py b/tests/signing/tests.py index 05c8b3dc74..05c8b3dc74 100644 --- a/tests/regressiontests/signing/tests.py +++ b/tests/signing/tests.py diff --git a/tests/regressiontests/select_related_onetoone/__init__.py b/tests/sites_framework/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/select_related_onetoone/__init__.py +++ b/tests/sites_framework/__init__.py diff --git a/tests/regressiontests/sites_framework/models.py b/tests/sites_framework/models.py index 55c4f4992e..55c4f4992e 100644 --- a/tests/regressiontests/sites_framework/models.py +++ b/tests/sites_framework/models.py diff --git a/tests/regressiontests/sites_framework/tests.py b/tests/sites_framework/tests.py index 8e664fd501..8e664fd501 100644 --- a/tests/regressiontests/sites_framework/tests.py +++ b/tests/sites_framework/tests.py diff --git a/tests/regressiontests/select_related_regress/__init__.py b/tests/special_headers/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/select_related_regress/__init__.py +++ b/tests/special_headers/__init__.py diff --git a/tests/regressiontests/special_headers/fixtures/data.xml b/tests/special_headers/fixtures/data.xml index 7e60d45199..7e60d45199 100644 --- a/tests/regressiontests/special_headers/fixtures/data.xml +++ b/tests/special_headers/fixtures/data.xml diff --git a/tests/regressiontests/special_headers/models.py b/tests/special_headers/models.py index e05c5a6920..e05c5a6920 100644 --- a/tests/regressiontests/special_headers/models.py +++ b/tests/special_headers/models.py diff --git a/tests/regressiontests/special_headers/templates/special_headers/article_detail.html b/tests/special_headers/templates/special_headers/article_detail.html index 3cbd38cb7e..3cbd38cb7e 100644 --- a/tests/regressiontests/special_headers/templates/special_headers/article_detail.html +++ b/tests/special_headers/templates/special_headers/article_detail.html diff --git a/tests/regressiontests/special_headers/tests.py b/tests/special_headers/tests.py index b890adbbad..b4b704ae21 100644 --- a/tests/regressiontests/special_headers/tests.py +++ b/tests/special_headers/tests.py @@ -6,7 +6,7 @@ from django.test.utils import override_settings @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class SpecialHeadersTest(TestCase): fixtures = ['data.xml'] - urls = 'regressiontests.special_headers.urls' + urls = 'special_headers.urls' def test_xheaders(self): user = User.objects.get(username='super') @@ -32,7 +32,7 @@ class SpecialHeadersTest(TestCase): self.client.login(username='super', password='secret') response = self.client.head('/special_headers/xview/func/') self.assertTrue('X-View' in response) - self.assertEqual(response['X-View'], 'regressiontests.special_headers.views.xview') + self.assertEqual(response['X-View'], 'special_headers.views.xview') user.is_staff = False user.save() response = self.client.head('/special_headers/xview/func/') @@ -50,7 +50,7 @@ class SpecialHeadersTest(TestCase): self.client.login(username='super', password='secret') response = self.client.head('/special_headers/xview/class/') self.assertTrue('X-View' in response) - self.assertEqual(response['X-View'], 'regressiontests.special_headers.views.XViewClass') + self.assertEqual(response['X-View'], 'special_headers.views.XViewClass') user.is_staff = False user.save() response = self.client.head('/special_headers/xview/class/') diff --git a/tests/regressiontests/special_headers/urls.py b/tests/special_headers/urls.py index f7ba141207..f7ba141207 100644 --- a/tests/regressiontests/special_headers/urls.py +++ b/tests/special_headers/urls.py diff --git a/tests/regressiontests/special_headers/views.py b/tests/special_headers/views.py index a8bbd6542e..a8bbd6542e 100644 --- a/tests/regressiontests/special_headers/views.py +++ b/tests/special_headers/views.py diff --git a/tests/regressiontests/serializers_regress/__init__.py b/tests/staticfiles_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/serializers_regress/__init__.py +++ b/tests/staticfiles_tests/__init__.py diff --git a/tests/regressiontests/servers/__init__.py b/tests/staticfiles_tests/apps/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/servers/__init__.py +++ b/tests/staticfiles_tests/apps/__init__.py diff --git a/tests/regressiontests/settings_tests/__init__.py b/tests/staticfiles_tests/apps/no_label/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/settings_tests/__init__.py +++ b/tests/staticfiles_tests/apps/no_label/__init__.py diff --git a/tests/regressiontests/staticfiles_tests/apps/test/models.py b/tests/staticfiles_tests/apps/no_label/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/models.py +++ b/tests/staticfiles_tests/apps/no_label/models.py diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/static/file2.txt b/tests/staticfiles_tests/apps/no_label/static/file2.txt index aa264cab9b..aa264cab9b 100644 --- a/tests/regressiontests/staticfiles_tests/apps/no_label/static/file2.txt +++ b/tests/staticfiles_tests/apps/no_label/static/file2.txt diff --git a/tests/regressiontests/signals_regress/__init__.py b/tests/staticfiles_tests/apps/test/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/signals_regress/__init__.py +++ b/tests/staticfiles_tests/apps/test/__init__.py diff --git a/tests/regressiontests/staticfiles_tests/models.py b/tests/staticfiles_tests/apps/test/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/models.py +++ b/tests/staticfiles_tests/apps/test/models.py diff --git a/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt b/tests/staticfiles_tests/apps/test/otherdir/odfile.txt index c62c93d162..c62c93d162 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/otherdir/odfile.txt +++ b/tests/staticfiles_tests/apps/test/otherdir/odfile.txt diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/CVS b/tests/staticfiles_tests/apps/test/static/test/CVS index cef6c23575..cef6c23575 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/static/test/CVS +++ b/tests/staticfiles_tests/apps/test/static/test/CVS diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/file.txt b/tests/staticfiles_tests/apps/test/static/test/file.txt index 169a206488..169a206488 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/static/test/file.txt +++ b/tests/staticfiles_tests/apps/test/static/test/file.txt diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/file1.txt b/tests/staticfiles_tests/apps/test/static/test/file1.txt index 9f9a8d92ab..9f9a8d92ab 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/static/test/file1.txt +++ b/tests/staticfiles_tests/apps/test/static/test/file1.txt diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css b/tests/staticfiles_tests/apps/test/static/test/nonascii.css index a5358f6ede..a5358f6ede 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/static/test/nonascii.css +++ b/tests/staticfiles_tests/apps/test/static/test/nonascii.css diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/test.ignoreme b/tests/staticfiles_tests/apps/test/static/test/test.ignoreme index d7df09c191..d7df09c191 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/static/test/test.ignoreme +++ b/tests/staticfiles_tests/apps/test/static/test/test.ignoreme diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/window.png b/tests/staticfiles_tests/apps/test/static/test/window.png Binary files differindex ba48325c0a..ba48325c0a 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/static/test/window.png +++ b/tests/staticfiles_tests/apps/test/static/test/window.png diff --git a/tests/regressiontests/templates/models.py b/tests/staticfiles_tests/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/templates/models.py +++ b/tests/staticfiles_tests/models.py diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/absolute.css b/tests/staticfiles_tests/project/documents/cached/absolute.css index 54467a362d..54467a362d 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/absolute.css +++ b/tests/staticfiles_tests/project/documents/cached/absolute.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.eot b/tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot index 7c58b2e622..7c58b2e622 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.eot +++ b/tests/staticfiles_tests/project/documents/cached/css/fonts/font.eot diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.svg b/tests/staticfiles_tests/project/documents/cached/css/fonts/font.svg index 0282375915..0282375915 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.svg +++ b/tests/staticfiles_tests/project/documents/cached/css/fonts/font.svg diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fragments.css b/tests/staticfiles_tests/project/documents/cached/css/fragments.css index 540d54b88d..540d54b88d 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fragments.css +++ b/tests/staticfiles_tests/project/documents/cached/css/fragments.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css b/tests/staticfiles_tests/project/documents/cached/css/ignored.css index fe7b022215..fe7b022215 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/ignored.css +++ b/tests/staticfiles_tests/project/documents/cached/css/ignored.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/img/window.png b/tests/staticfiles_tests/project/documents/cached/css/img/window.png Binary files differindex ba48325c0a..ba48325c0a 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/img/window.png +++ b/tests/staticfiles_tests/project/documents/cached/css/img/window.png diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/window.css b/tests/staticfiles_tests/project/documents/cached/css/window.css index 9fea4e8a5e..9fea4e8a5e 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/window.css +++ b/tests/staticfiles_tests/project/documents/cached/css/window.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/denorm.css b/tests/staticfiles_tests/project/documents/cached/denorm.css index d6567b00dd..d6567b00dd 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/denorm.css +++ b/tests/staticfiles_tests/project/documents/cached/denorm.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/img/relative.png b/tests/staticfiles_tests/project/documents/cached/img/relative.png Binary files differindex ba48325c0a..ba48325c0a 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/img/relative.png +++ b/tests/staticfiles_tests/project/documents/cached/img/relative.png diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/import.css b/tests/staticfiles_tests/project/documents/cached/import.css index 6bc7ce04c4..6bc7ce04c4 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/import.css +++ b/tests/staticfiles_tests/project/documents/cached/import.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/other.css b/tests/staticfiles_tests/project/documents/cached/other.css index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/other.css +++ b/tests/staticfiles_tests/project/documents/cached/other.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/relative.css b/tests/staticfiles_tests/project/documents/cached/relative.css index 95d1f4abb8..95d1f4abb8 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/relative.css +++ b/tests/staticfiles_tests/project/documents/cached/relative.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/styles.css b/tests/staticfiles_tests/project/documents/cached/styles.css index ca28fc35cd..ca28fc35cd 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/styles.css +++ b/tests/staticfiles_tests/project/documents/cached/styles.css diff --git a/tests/staticfiles_tests/project/documents/cached/styles_insensitive.css b/tests/staticfiles_tests/project/documents/cached/styles_insensitive.css new file mode 100644 index 0000000000..57904ad415 --- /dev/null +++ b/tests/staticfiles_tests/project/documents/cached/styles_insensitive.css @@ -0,0 +1 @@ +@IMporT uRL("other.css");
\ No newline at end of file diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/url.css b/tests/staticfiles_tests/project/documents/cached/url.css index 184e254004..184e254004 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/cached/url.css +++ b/tests/staticfiles_tests/project/documents/cached/url.css diff --git a/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt b/tests/staticfiles_tests/project/documents/subdir/test.txt index 04326a212d..04326a212d 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/subdir/test.txt +++ b/tests/staticfiles_tests/project/documents/subdir/test.txt diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test.txt b/tests/staticfiles_tests/project/documents/test.txt index 04326a212d..04326a212d 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/test.txt +++ b/tests/staticfiles_tests/project/documents/test.txt diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test/camelCase.txt b/tests/staticfiles_tests/project/documents/test/camelCase.txt index b4f8882668..b4f8882668 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/test/camelCase.txt +++ b/tests/staticfiles_tests/project/documents/test/camelCase.txt diff --git a/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt b/tests/staticfiles_tests/project/documents/test/file.txt index fdeaa23254..fdeaa23254 100644 --- a/tests/regressiontests/staticfiles_tests/project/documents/test/file.txt +++ b/tests/staticfiles_tests/project/documents/test/file.txt diff --git a/tests/regressiontests/staticfiles_tests/project/prefixed/test.txt b/tests/staticfiles_tests/project/prefixed/test.txt index d3a017895c..d3a017895c 100644 --- a/tests/regressiontests/staticfiles_tests/project/prefixed/test.txt +++ b/tests/staticfiles_tests/project/prefixed/test.txt diff --git a/tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt b/tests/staticfiles_tests/project/site_media/media/media-file.txt index 466922d07a..466922d07a 100644 --- a/tests/regressiontests/staticfiles_tests/project/site_media/media/media-file.txt +++ b/tests/staticfiles_tests/project/site_media/media/media-file.txt diff --git a/tests/regressiontests/staticfiles_tests/project/site_media/static/testfile.txt b/tests/staticfiles_tests/project/site_media/static/testfile.txt index 4d92dbe1ad..4d92dbe1ad 100644 --- a/tests/regressiontests/staticfiles_tests/project/site_media/static/testfile.txt +++ b/tests/staticfiles_tests/project/site_media/static/testfile.txt diff --git a/tests/regressiontests/staticfiles_tests/storage.py b/tests/staticfiles_tests/storage.py index 4d49e6fbb2..4d49e6fbb2 100644 --- a/tests/regressiontests/staticfiles_tests/storage.py +++ b/tests/staticfiles_tests/storage.py diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/staticfiles_tests/tests.py index 90c8621d0b..0391b8b018 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/staticfiles_tests/tests.py @@ -195,21 +195,33 @@ class TestFindStatic(CollectionTestCase, TestDefaults): call_command('findstatic', filepath, all=False, verbosity=0, stdout=out) out.seek(0) lines = [l.strip() for l in out.readlines()] - with codecs.open(force_text(lines[1].strip()), "r", "utf-8") as f: + with codecs.open(force_text(lines[0].strip()), "r", "utf-8") as f: return f.read() def test_all_files(self): """ - Test that findstatic returns all candidate files if run without --first. + Test that findstatic returns all candidate files if run without --first and -v1. """ out = six.StringIO() - call_command('findstatic', 'test/file.txt', verbosity=0, stdout=out) + call_command('findstatic', 'test/file.txt', verbosity=1, stdout=out) out.seek(0) lines = [l.strip() for l in out.readlines()] self.assertEqual(len(lines), 3) # three because there is also the "Found <file> here" line self.assertIn('project', force_text(lines[1])) self.assertIn('apps', force_text(lines[2])) + def test_all_files_less_verbose(self): + """ + Test that findstatic returns all candidate files if run without --first and -v0. + """ + out = six.StringIO() + call_command('findstatic', 'test/file.txt', verbosity=0, stdout=out) + out.seek(0) + lines = [l.strip() for l in out.readlines()] + self.assertEqual(len(lines), 2) + self.assertIn('project', force_text(lines[0])) + self.assertIn('apps', force_text(lines[1])) + class TestCollection(CollectionTestCase, TestDefaults): """ @@ -287,8 +299,8 @@ class TestCollectionFilesOverride(CollectionTestCase): Check for proper handling of apps order in INSTALLED_APPS even if file modification dates are in different order: - 'regressiontests.staticfiles_tests.apps.test', - 'regressiontests.staticfiles_tests.apps.no_label', + 'staticfiles_tests.apps.test', + 'staticfiles_tests.apps.no_label', """ def setUp(self): @@ -338,7 +350,7 @@ class TestCollectionFilesOverride(CollectionTestCase): @override_settings( - STATICFILES_STORAGE='regressiontests.staticfiles_tests.storage.DummyStorage', + STATICFILES_STORAGE='staticfiles_tests.storage.DummyStorage', ) class TestCollectionNonLocalStorage(CollectionTestCase, TestNoFilesCreated): """ @@ -530,10 +542,18 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, cache_validator.validate_key(cache_key) self.assertEqual(cache_key, 'staticfiles:821ea71ef36f95b3922a77f7364670e7') + def test_css_import_case_insensitive(self): + relpath = self.cached_file_path("cached/styles_insensitive.css") + self.assertEqual(relpath, "cached/styles_insensitive.2f0151cca872.css") + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertNotIn(b"cached/other.css", content) + self.assertIn(b"other.d41d8cd98f00.css", content) + # we set DEBUG to False here since the template tag wouldn't work otherwise @override_settings(**dict(TEST_SETTINGS, - STATICFILES_STORAGE='regressiontests.staticfiles_tests.storage.SimpleCachedStaticFilesStorage', + STATICFILES_STORAGE='staticfiles_tests.storage.SimpleCachedStaticFilesStorage', DEBUG=False, )) class TestCollectionSimpleCachedStorage(BaseCollectionTestCase, @@ -593,7 +613,7 @@ class TestServeStatic(StaticFilesTestCase): """ Test static asset serving view. """ - urls = 'regressiontests.staticfiles_tests.urls.default' + urls = 'staticfiles_tests.urls.default' def _response(self, filepath): return self.client.get( @@ -630,7 +650,7 @@ class TestServeStaticWithURLHelper(TestServeStatic, TestDefaults): """ Test static asset serving view with staticfiles_urlpatterns helper. """ - urls = 'regressiontests.staticfiles_tests.urls.helper' + urls = 'staticfiles_tests.urls.helper' class TestServeAdminMedia(TestServeStatic): diff --git a/tests/regressiontests/signed_cookies_tests/__init__.py b/tests/staticfiles_tests/urls/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/signed_cookies_tests/__init__.py +++ b/tests/staticfiles_tests/urls/__init__.py diff --git a/tests/regressiontests/staticfiles_tests/urls/default.py b/tests/staticfiles_tests/urls/default.py index e50a9e2907..e50a9e2907 100644 --- a/tests/regressiontests/staticfiles_tests/urls/default.py +++ b/tests/staticfiles_tests/urls/default.py diff --git a/tests/regressiontests/staticfiles_tests/urls/helper.py b/tests/staticfiles_tests/urls/helper.py index e4951d1afb..e4951d1afb 100644 --- a/tests/regressiontests/staticfiles_tests/urls/helper.py +++ b/tests/staticfiles_tests/urls/helper.py diff --git a/tests/regressiontests/signing/__init__.py b/tests/str/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/signing/__init__.py +++ b/tests/str/__init__.py diff --git a/tests/modeltests/str/models.py b/tests/str/models.py index 1c158ea008..1c158ea008 100644 --- a/tests/modeltests/str/models.py +++ b/tests/str/models.py diff --git a/tests/modeltests/str/tests.py b/tests/str/tests.py index 31869583aa..bd85c48d05 100644 --- a/tests/modeltests/str/tests.py +++ b/tests/str/tests.py @@ -28,7 +28,7 @@ class SimpleTests(TestCase): headline='Girl wins €12.500 in lottery', pub_date=datetime.datetime(2005, 7, 28) ) - self.assertRaisesRegexp(RuntimeError, "Did you apply " + six.assertRaisesRegex(self, RuntimeError, "Did you apply " "@python_2_unicode_compatible without defining __str__\?", str, a) def test_international(self): diff --git a/tests/regressiontests/sites_framework/__init__.py b/tests/string_lookup/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/sites_framework/__init__.py +++ b/tests/string_lookup/__init__.py diff --git a/tests/regressiontests/string_lookup/models.py b/tests/string_lookup/models.py index a2d64cd0b2..a2d64cd0b2 100644 --- a/tests/regressiontests/string_lookup/models.py +++ b/tests/string_lookup/models.py diff --git a/tests/regressiontests/string_lookup/tests.py b/tests/string_lookup/tests.py index 02f766adce..02f766adce 100644 --- a/tests/regressiontests/string_lookup/tests.py +++ b/tests/string_lookup/tests.py diff --git a/tests/regressiontests/special_headers/__init__.py b/tests/swappable_models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/special_headers/__init__.py +++ b/tests/swappable_models/__init__.py diff --git a/tests/regressiontests/swappable_models/models.py b/tests/swappable_models/models.py index 92692d4396..92692d4396 100644 --- a/tests/regressiontests/swappable_models/models.py +++ b/tests/swappable_models/models.py diff --git a/tests/regressiontests/swappable_models/tests.py b/tests/swappable_models/tests.py index 75644e32aa..d3d61341d2 100644 --- a/tests/regressiontests/swappable_models/tests.py +++ b/tests/swappable_models/tests.py @@ -9,6 +9,8 @@ from django.db.models.loading import cache from django.test import TestCase from django.test.utils import override_settings +from swappable_models.models import Article + class SwappableModelTests(TestCase): def setUp(self): @@ -44,3 +46,13 @@ class SwappableModelTests(TestCase): for ct in ContentType.objects.all()] self.assertIn(('swappable_models', 'alternatearticle'), apps_models) self.assertNotIn(('swappable_models', 'article'), apps_models) + + @override_settings(TEST_ARTICLE_MODEL='swappable_models.article') + def test_case_insensitive(self): + "Model names are case insensitive. Check that model swapping honors this." + try: + Article.objects.all() + except AttributeError: + self.fail('Swappable model names should be case insensitive.') + + self.assertIsNone(Article._meta.swapped) diff --git a/tests/regressiontests/staticfiles_tests/__init__.py b/tests/syndication/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/__init__.py +++ b/tests/syndication/__init__.py diff --git a/tests/regressiontests/syndication/feeds.py b/tests/syndication/feeds.py index 04a67f4bdb..0956820bf0 100644 --- a/tests/regressiontests/syndication/feeds.py +++ b/tests/syndication/feeds.py @@ -42,6 +42,19 @@ class TestRss2Feed(views.Feed): item_copyright = 'Copyright (c) 2007, Sally Smith' +class TestRss2FeedWithGuidIsPermaLinkTrue(TestRss2Feed): + def item_guid_is_permalink(self, item): + return True + + +class TestRss2FeedWithGuidIsPermaLinkFalse(TestRss2Feed): + def item_guid(self, item): + return str(item.pk) + + def item_guid_is_permalink(self, item): + return False + + class TestRss091Feed(TestRss2Feed): feed_type = feedgenerator.RssUserland091Feed @@ -84,6 +97,19 @@ class TemplateFeed(TestRss2Feed): return "Not in a template" +class TemplateContextFeed(TestRss2Feed): + """ + A feed to test custom context data in templates for title or description. + """ + title_template = 'syndication/title_context.html' + description_template = 'syndication/description_context.html' + + def get_context_data(self, **kwargs): + context = super(TemplateContextFeed, self).get_context_data(**kwargs) + context['foo'] = 'bar' + return context + + class NaiveDatesFeed(TestAtomFeed): """ A feed with naive (non-timezone-aware) dates. diff --git a/tests/regressiontests/syndication/fixtures/feeddata.json b/tests/syndication/fixtures/feeddata.json index 167115c925..167115c925 100644 --- a/tests/regressiontests/syndication/fixtures/feeddata.json +++ b/tests/syndication/fixtures/feeddata.json diff --git a/tests/regressiontests/syndication/models.py b/tests/syndication/models.py index 10b3fe3a0c..10b3fe3a0c 100644 --- a/tests/regressiontests/syndication/models.py +++ b/tests/syndication/models.py diff --git a/tests/regressiontests/syndication/templates/syndication/description.html b/tests/syndication/templates/syndication/description.html index 85ec82c8df..85ec82c8df 100644 --- a/tests/regressiontests/syndication/templates/syndication/description.html +++ b/tests/syndication/templates/syndication/description.html diff --git a/tests/syndication/templates/syndication/description_context.html b/tests/syndication/templates/syndication/description_context.html new file mode 100644 index 0000000000..319d84b1b0 --- /dev/null +++ b/tests/syndication/templates/syndication/description_context.html @@ -0,0 +1 @@ +{{ obj }} (foo is {{ foo }})
\ No newline at end of file diff --git a/tests/regressiontests/syndication/templates/syndication/title.html b/tests/syndication/templates/syndication/title.html index eb17969714..eb17969714 100644 --- a/tests/regressiontests/syndication/templates/syndication/title.html +++ b/tests/syndication/templates/syndication/title.html diff --git a/tests/syndication/templates/syndication/title_context.html b/tests/syndication/templates/syndication/title_context.html new file mode 100644 index 0000000000..319d84b1b0 --- /dev/null +++ b/tests/syndication/templates/syndication/title_context.html @@ -0,0 +1 @@ +{{ obj }} (foo is {{ foo }})
\ No newline at end of file diff --git a/tests/regressiontests/syndication/tests.py b/tests/syndication/tests.py index 10413b4ddd..1627f7182b 100644 --- a/tests/regressiontests/syndication/tests.py +++ b/tests/syndication/tests.py @@ -35,7 +35,7 @@ class SyndicationFeedTest(FeedTestCase): """ Tests for the high-level syndication feed framework. """ - urls = 'regressiontests.syndication.urls' + urls = 'syndication.urls' def test_rss2_feed(self): """ @@ -103,9 +103,43 @@ class SyndicationFeedTest(FeedTestCase): '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']) + # Assert that <guid> does not have any 'isPermaLink' attribute + self.assertIsNone(item.getElementsByTagName( + 'guid')[0].attributes.get('isPermaLink')) + + def test_rss2_feed_guid_permalink_false(self): + """ + Test if the 'isPermaLink' attribute of <guid> element of an item + in the RSS feed is 'false'. + """ + response = self.client.get( + '/syndication/rss2/guid_ispermalink_false/') + doc = minidom.parseString(response.content) + chan = doc.getElementsByTagName( + 'rss')[0].getElementsByTagName('channel')[0] + items = chan.getElementsByTagName('item') + for item in items: + self.assertEqual( + item.getElementsByTagName('guid')[0].attributes.get( + 'isPermaLink').value, "false") + + def test_rss2_feed_guid_permalink_true(self): + """ + Test if the 'isPermaLink' attribute of <guid> element of an item + in the RSS feed is 'true'. + """ + response = self.client.get( + '/syndication/rss2/guid_ispermalink_true/') + doc = minidom.parseString(response.content) + chan = doc.getElementsByTagName( + 'rss')[0].getElementsByTagName('channel')[0] + items = chan.getElementsByTagName('item') + for item in items: + self.assertEqual( + item.getElementsByTagName('guid')[0].attributes.get( + 'isPermaLink').value, "true") def test_rss091_feed(self): """ @@ -289,6 +323,22 @@ class SyndicationFeedTest(FeedTestCase): 'link': 'http://example.com/blog/1/', }) + def test_template_context_feed(self): + """ + Test that custom context data can be passed to templates for title + and description. + """ + response = self.client.get('/syndication/template_context/') + doc = minidom.parseString(response.content) + feed = doc.getElementsByTagName('rss')[0] + chan = feed.getElementsByTagName('channel')[0] + items = chan.getElementsByTagName('item') + + self.assertChildNodeContent(items[0], { + 'title': 'My first entry (foo is bar)', + 'description': 'My first entry (foo is bar)', + }) + def test_add_domain(self): """ Test add_domain() prefixes domains onto the correct URLs. diff --git a/tests/regressiontests/syndication/urls.py b/tests/syndication/urls.py index 57f9d81a73..1dd7e92332 100644 --- a/tests/regressiontests/syndication/urls.py +++ b/tests/syndication/urls.py @@ -8,6 +8,10 @@ from . import feeds urlpatterns = patterns('django.contrib.syndication.views', (r'^syndication/complex/(?P<foo>.*)/$', feeds.ComplexFeed()), (r'^syndication/rss2/$', feeds.TestRss2Feed()), + (r'^syndication/rss2/guid_ispermalink_true/$', + feeds.TestRss2FeedWithGuidIsPermaLinkTrue()), + (r'^syndication/rss2/guid_ispermalink_false/$', + feeds.TestRss2FeedWithGuidIsPermaLinkFalse()), (r'^syndication/rss091/$', feeds.TestRss091Feed()), (r'^syndication/no_pubdate/$', feeds.TestNoPubdateFeed()), (r'^syndication/atom/$', feeds.TestAtomFeed()), @@ -17,4 +21,5 @@ urlpatterns = patterns('django.contrib.syndication.views', (r'^syndication/feedurl/$', feeds.TestFeedUrlFeed()), (r'^syndication/articles/$', feeds.ArticlesFeed()), (r'^syndication/template/$', feeds.TemplateFeed()), + (r'^syndication/template_context/$', feeds.TemplateContextFeed()), ) diff --git a/tests/regressiontests/staticfiles_tests/apps/__init__.py b/tests/tablespaces/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/apps/__init__.py +++ b/tests/tablespaces/__init__.py diff --git a/tests/modeltests/tablespaces/models.py b/tests/tablespaces/models.py index 7d7b96380c..7d7b96380c 100644 --- a/tests/modeltests/tablespaces/models.py +++ b/tests/tablespaces/models.py diff --git a/tests/modeltests/tablespaces/tests.py b/tests/tablespaces/tests.py index 1eaddef079..1eaddef079 100644 --- a/tests/modeltests/tablespaces/tests.py +++ b/tests/tablespaces/tests.py diff --git a/tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py b/tests/template_tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/apps/no_label/__init__.py +++ b/tests/template_tests/__init__.py diff --git a/tests/regressiontests/templates/alternate_urls.py b/tests/template_tests/alternate_urls.py index fa4985a9dc..fa4985a9dc 100644 --- a/tests/regressiontests/templates/alternate_urls.py +++ b/tests/template_tests/alternate_urls.py diff --git a/tests/regressiontests/templates/eggs/tagsegg.egg b/tests/template_tests/eggs/tagsegg.egg Binary files differindex 3941914b81..3941914b81 100644 --- a/tests/regressiontests/templates/eggs/tagsegg.egg +++ b/tests/template_tests/eggs/tagsegg.egg diff --git a/tests/regressiontests/templates/filters.py b/tests/template_tests/filters.py index 7ba1681fd5..7ba1681fd5 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/template_tests/filters.py diff --git a/tests/regressiontests/templates/loaders.py b/tests/template_tests/loaders.py index b77965203f..b77965203f 100644 --- a/tests/regressiontests/templates/loaders.py +++ b/tests/template_tests/loaders.py diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/template_tests/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/template_tests/models.py diff --git a/tests/regressiontests/templates/templates/broken_base.html b/tests/template_tests/templates/broken_base.html index aa41f44de2..aa41f44de2 100644 --- a/tests/regressiontests/templates/templates/broken_base.html +++ b/tests/template_tests/templates/broken_base.html diff --git a/tests/regressiontests/templates/templates/first/test.html b/tests/template_tests/templates/first/test.html index 6029fe5507..6029fe5507 100644 --- a/tests/regressiontests/templates/templates/first/test.html +++ b/tests/template_tests/templates/first/test.html diff --git a/tests/regressiontests/templates/templates/inclusion.html b/tests/template_tests/templates/inclusion.html index 4000d3aadb..4000d3aadb 100644 --- a/tests/regressiontests/templates/templates/inclusion.html +++ b/tests/template_tests/templates/inclusion.html diff --git a/tests/regressiontests/templates/templates/response.html b/tests/template_tests/templates/response.html index 96ab97f54f..96ab97f54f 100644 --- a/tests/regressiontests/templates/templates/response.html +++ b/tests/template_tests/templates/response.html diff --git a/tests/regressiontests/templates/templates/second/test.html b/tests/template_tests/templates/second/test.html index d9b316f465..d9b316f465 100644 --- a/tests/regressiontests/templates/templates/second/test.html +++ b/tests/template_tests/templates/second/test.html diff --git a/tests/regressiontests/templates/templates/ssi include with spaces.html b/tests/template_tests/templates/ssi include with spaces.html index 1c85648861..1c85648861 100644 --- a/tests/regressiontests/templates/templates/ssi include with spaces.html +++ b/tests/template_tests/templates/ssi include with spaces.html diff --git a/tests/regressiontests/templates/templates/ssi_include.html b/tests/template_tests/templates/ssi_include.html index 58d5926fbb..58d5926fbb 100644 --- a/tests/regressiontests/templates/templates/ssi_include.html +++ b/tests/template_tests/templates/ssi_include.html diff --git a/tests/regressiontests/templates/templates/test_context.html b/tests/template_tests/templates/test_context.html index a100f03de6..a100f03de6 100644 --- a/tests/regressiontests/templates/templates/test_context.html +++ b/tests/template_tests/templates/test_context.html diff --git a/tests/regressiontests/templates/templates/test_extends_error.html b/tests/template_tests/templates/test_extends_error.html index fc74690de6..fc74690de6 100644 --- a/tests/regressiontests/templates/templates/test_extends_error.html +++ b/tests/template_tests/templates/test_extends_error.html diff --git a/tests/regressiontests/templates/templates/test_incl_tag_current_app.html b/tests/template_tests/templates/test_incl_tag_current_app.html index ab2fe63b6f..ab2fe63b6f 100644 --- a/tests/regressiontests/templates/templates/test_incl_tag_current_app.html +++ b/tests/template_tests/templates/test_incl_tag_current_app.html diff --git a/tests/regressiontests/templates/templates/test_incl_tag_use_l10n.html b/tests/template_tests/templates/test_incl_tag_use_l10n.html index 3054960d16..3054960d16 100644 --- a/tests/regressiontests/templates/templates/test_incl_tag_use_l10n.html +++ b/tests/template_tests/templates/test_incl_tag_use_l10n.html diff --git a/tests/regressiontests/templates/templates/test_include_error.html b/tests/template_tests/templates/test_include_error.html index 6db959380e..6db959380e 100644 --- a/tests/regressiontests/templates/templates/test_include_error.html +++ b/tests/template_tests/templates/test_include_error.html diff --git a/tests/regressiontests/staticfiles_tests/apps/test/__init__.py b/tests/template_tests/templatetags/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/apps/test/__init__.py +++ b/tests/template_tests/templatetags/__init__.py diff --git a/tests/regressiontests/templates/templatetags/bad_tag.py b/tests/template_tests/templatetags/bad_tag.py index 3cceb31eb0..3cceb31eb0 100644 --- a/tests/regressiontests/templates/templatetags/bad_tag.py +++ b/tests/template_tests/templatetags/bad_tag.py diff --git a/tests/regressiontests/templates/templatetags/broken_tag.py b/tests/template_tests/templatetags/broken_tag.py index d69ddaeb2c..d69ddaeb2c 100644 --- a/tests/regressiontests/templates/templatetags/broken_tag.py +++ b/tests/template_tests/templatetags/broken_tag.py diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/template_tests/templatetags/custom.py index 3f51353880..32035ab59e 100644 --- a/tests/regressiontests/templates/templatetags/custom.py +++ b/tests/template_tests/templatetags/custom.py @@ -12,6 +12,13 @@ register = template.Library() def trim(value, num): return value[:num] +@register.filter +def noop(value, param=None): + """A noop filter that always return its first argument and does nothing with + its second (optional) one. + Useful for testing out whitespace in filter arguments (see #19882).""" + return value + @register.simple_tag def no_params(): """Expected no_params __doc__""" diff --git a/tests/regressiontests/staticfiles_tests/urls/__init__.py b/tests/template_tests/templatetags/subpackage/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/staticfiles_tests/urls/__init__.py +++ b/tests/template_tests/templatetags/subpackage/__init__.py diff --git a/tests/regressiontests/templates/templatetags/subpackage/echo.py b/tests/template_tests/templatetags/subpackage/echo.py index 0e4e862887..0e4e862887 100644 --- a/tests/regressiontests/templates/templatetags/subpackage/echo.py +++ b/tests/template_tests/templatetags/subpackage/echo.py diff --git a/tests/regressiontests/templates/templatetags/subpackage/echo_invalid.py b/tests/template_tests/templatetags/subpackage/echo_invalid.py index c12ea65507..c12ea65507 100644 --- a/tests/regressiontests/templates/templatetags/subpackage/echo_invalid.py +++ b/tests/template_tests/templatetags/subpackage/echo_invalid.py diff --git a/tests/regressiontests/templates/callables.py b/tests/template_tests/test_callables.py index 882a8c6e06..882a8c6e06 100644 --- a/tests/regressiontests/templates/callables.py +++ b/tests/template_tests/test_callables.py diff --git a/tests/regressiontests/templates/context.py b/tests/template_tests/test_context.py index 05c1dd57b9..05c1dd57b9 100644 --- a/tests/regressiontests/templates/context.py +++ b/tests/template_tests/test_context.py diff --git a/tests/regressiontests/templates/custom.py b/tests/template_tests/test_custom.py index 4aea08237d..4aea08237d 100644 --- a/tests/regressiontests/templates/custom.py +++ b/tests/template_tests/test_custom.py diff --git a/tests/regressiontests/templates/nodelist.py b/tests/template_tests/test_nodelist.py index 97aa5af6a7..97aa5af6a7 100644 --- a/tests/regressiontests/templates/nodelist.py +++ b/tests/template_tests/test_nodelist.py diff --git a/tests/regressiontests/templates/parser.py b/tests/template_tests/test_parser.py index 6c9deee9ff..9422da80d7 100644 --- a/tests/regressiontests/templates/parser.py +++ b/tests/template_tests/test_parser.py @@ -4,8 +4,10 @@ Testing some internals of the template processing. These are *not* examples to b from __future__ import unicode_literals from django.template import (TokenParser, FilterExpression, Parser, Variable, - TemplateSyntaxError) + Template, TemplateSyntaxError) +from django.test.utils import override_settings from django.utils.unittest import TestCase +from django.utils import six class ParserTests(TestCase): @@ -83,3 +85,11 @@ class ParserTests(TestCase): self.assertRaises(TemplateSyntaxError, Variable, "article._hidden" ) + + @override_settings(DEBUG=True, TEMPLATE_DEBUG=True) + def test_compile_filter_error(self): + # regression test for #19819 + msg = "Could not parse the remainder: '@bar' from 'foo@bar'" + with six.assertRaisesRegex(self, TemplateSyntaxError, msg) as cm: + Template("{% if 1 %}{{ foo@bar }}{% endif %}") + self.assertEqual(cm.exception.django_template_source[1], (10, 23)) diff --git a/tests/regressiontests/templates/response.py b/tests/template_tests/test_response.py index c4da50af6b..6acc45626f 100644 --- a/tests/regressiontests/templates/response.py +++ b/tests/template_tests/test_response.py @@ -15,13 +15,13 @@ from django.utils._os import upath def test_processor(request): return {'processors': 'yes'} -test_processor_name = 'regressiontests.templates.response.test_processor' +test_processor_name = 'template_tests.test_response.test_processor' # A test middleware that installs a temporary URLConf class CustomURLConfMiddleware(object): def process_request(self, request): - request.urlconf = 'regressiontests.templates.alternate_urls' + request.urlconf = 'template_tests.alternate_urls' class SimpleTemplateResponseTest(TestCase): @@ -298,12 +298,12 @@ class TemplateResponseTest(TestCase): class CustomURLConfTest(TestCase): - urls = 'regressiontests.templates.urls' + urls = 'template_tests.urls' def setUp(self): self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) + [ - 'regressiontests.templates.response.CustomURLConfMiddleware' + 'template_tests.test_response.CustomURLConfMiddleware' ] def tearDown(self): @@ -316,7 +316,7 @@ class CustomURLConfTest(TestCase): class CacheMiddlewareTest(TestCase): - urls = 'regressiontests.templates.alternate_urls' + urls = 'template_tests.alternate_urls' def setUp(self): self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES diff --git a/tests/regressiontests/templates/smartif.py b/tests/template_tests/test_smartif.py index 3a705ca663..3a705ca663 100644 --- a/tests/regressiontests/templates/smartif.py +++ b/tests/template_tests/test_smartif.py diff --git a/tests/regressiontests/templates/unicode.py b/tests/template_tests/test_unicode.py index 7cb2a28d15..7cb2a28d15 100644 --- a/tests/regressiontests/templates/unicode.py +++ b/tests/template_tests/test_unicode.py diff --git a/tests/regressiontests/templates/tests.py b/tests/template_tests/tests.py index d21434a12e..da1562d2d5 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/template_tests/tests.py @@ -17,12 +17,12 @@ try: from urllib.parse import urljoin except ImportError: # Python 2 from urlparse import urljoin +import warnings from django import template -from django.template import (base as template_base, Context, RequestContext, - Template) from django.core import urlresolvers -from django.template import loader +from django.template import (base as template_base, loader, Context, + RequestContext, Template, TemplateSyntaxError) from django.template.loaders import app_directories, filesystem, cached from django.test import RequestFactory, TestCase from django.test.utils import (setup_test_template_loader, @@ -36,14 +36,14 @@ from django.utils.safestring import mark_safe from django.utils import six from django.utils.tzinfo import LocalTimezone -from .callables import CallableVariablesTests -from .context import ContextTests -from .custom import CustomTagTests, CustomFilterTests -from .parser import ParserTests -from .unicode import UnicodeTests -from .nodelist import NodelistTest, ErrorIndexTest -from .smartif import SmartIfTests -from .response import (TemplateResponseTest, CacheMiddlewareTest, +from .test_callables import CallableVariablesTests +from .test_context import ContextTests +from .test_custom import CustomTagTests, CustomFilterTests +from .test_parser import ParserTests +from .test_unicode import UnicodeTests +from .test_nodelist import NodelistTest, ErrorIndexTest +from .test_smartif import SmartIfTests +from .test_response import (TemplateResponseTest, CacheMiddlewareTest, SimpleTemplateResponseTest, CustomURLConfTest) try: @@ -54,6 +54,12 @@ except ImportError as e: else: raise +# NumPy installed? +try: + import numpy +except ImportError: + numpy = False + from . import filters ################################# @@ -359,24 +365,38 @@ class Templates(TestCase): @override_settings(SETTINGS_MODULE=None, TEMPLATE_DEBUG=True) def test_url_reverse_no_settings_module(self): # Regression test for #9005 - from django.template import Template, Context - t = Template('{% url will_not_match %}') c = Context() with self.assertRaises(urlresolvers.NoReverseMatch): t.render(c) + @override_settings(TEMPLATE_STRING_IF_INVALID='%s is invalid', SETTINGS_MODULE='also_something') + def test_url_reverse_view_name(self): + # Regression test for #19827 + t = Template('{% url will_not_match %}') + c = Context() + try: + t.render(c) + except urlresolvers.NoReverseMatch: + tb = sys.exc_info()[2] + depth = 0 + while tb.tb_next is not None: + tb = tb.tb_next + depth += 1 + self.assertTrue(depth > 5, + "The traceback context was lost when reraising the traceback. See #19827") + def test_url_explicit_exception_for_old_syntax_at_run_time(self): # Regression test for #19280 t = Template('{% url path.to.view %}') # not quoted = old syntax c = Context() - with self.assertRaisesRegexp(urlresolvers.NoReverseMatch, + with six.assertRaisesRegex(self, urlresolvers.NoReverseMatch, "The syntax changed in Django 1.5, see the docs."): t.render(c) def test_url_explicit_exception_for_old_syntax_at_compile_time(self): # Regression test for #19392 - with self.assertRaisesRegexp(template.TemplateSyntaxError, + with six.assertRaisesRegex(self, template.TemplateSyntaxError, "The syntax of 'url' changed in Django 1.5, see the docs."): t = Template('{% url my-view %}') # not a variable = old syntax @@ -395,12 +415,44 @@ class Templates(TestCase): def test_invalid_block_suggestion(self): # See #7876 - from django.template import Template, TemplateSyntaxError try: t = Template("{% if 1 %}lala{% endblock %}{% endif %}") except TemplateSyntaxError as e: self.assertEqual(e.args[0], "Invalid block tag: 'endblock', expected 'elif', 'else' or 'endif'") + def test_ifchanged_concurrency(self): + # Tests for #15849 + template = Template('[0{% for x in foo %},{% with var=get_value %}{% ifchanged %}{{ var }}{% endifchanged %}{% endwith %}{% endfor %}]') + + # Using generator to mimic concurrency. + # The generator is not passed to the 'for' loop, because it does a list(values) + # instead, call gen.next() in the template to control the generator. + def gen(): + yield 1 + yield 2 + # Simulate that another thread is now rendering. + # When the IfChangeNode stores state at 'self' it stays at '3' and skip the last yielded value below. + iter2 = iter([1, 2, 3]) + output2 = template.render(Context({'foo': range(3), 'get_value': lambda: next(iter2)})) + self.assertEqual(output2, '[0,1,2,3]', 'Expected [0,1,2,3] in second parallel template, got {0}'.format(output2)) + yield 3 + + gen1 = gen() + output1 = template.render(Context({'foo': range(3), 'get_value': lambda: next(gen1)})) + self.assertEqual(output1, '[0,1,2,3]', 'Expected [0,1,2,3] in first template, got {0}'.format(output1)) + + def test_cache_regression_20130(self): + t = Template('{% load cache %}{% cache 1 regression_20130 %}foo{% endcache %}') + cachenode = t.nodelist[1] + self.assertEqual(cachenode.fragment_name, 'regression_20130') + + def test_ifchanged_render_once(self): + """ Test for ticket #19890. The content of ifchanged template tag was + rendered twice.""" + template = Template('{% load cycle from future %}{% ifchanged %}{% cycle "1st time" "2nd time" %}{% endifchanged %}') + output = template.render(Context({})) + self.assertEqual(output, '1st time') + def test_templates(self): template_tests = self.get_template_tests() filter_tests = filters.get_filter_tests() @@ -435,7 +487,7 @@ class Templates(TestCase): # 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', + urlresolvers.reverse('template_tests.views.client_action', kwargs={'id':0,'action':"update"}) for name, vals in tests: @@ -473,7 +525,10 @@ class Templates(TestCase): for is_cached in (False, True): try: try: - test_template = loader.get_template(name) + with warnings.catch_warnings(): + # Ignore pending deprecations of the old syntax of the 'cycle' and 'firstof' tags. + warnings.filterwarnings("ignore", category=PendingDeprecationWarning, module='django.template.base') + test_template = loader.get_template(name) except ShouldNotExecuteException: failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s', TEMPLATE_DEBUG=%s): %s -- FAILED. Template loading invoked method that shouldn't have been invoked." % (is_cached, invalid_str, template_debug, name)) @@ -767,6 +822,11 @@ class Templates(TestCase): 'cycle23': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ abc }}{{ x }}{% endfor %}", {'values': [1,2,3,4]}, "a1b2c3a4"), 'included-cycle': ('{{ abc }}', {'abc': 'xxx'}, 'xxx'), 'cycle24': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{% include 'included-cycle' %}{% endfor %}", {'values': [1,2,3,4]}, "abca"), + 'cycle25': ('{% cycle a as abc %}', {'a': '<'}, '<'), + + 'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<>'), + 'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'), + 'cycle28': ('{% load cycle from future %}{% cycle a|safe b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<>'), ### EXCEPTIONS ############################################################ @@ -798,7 +858,12 @@ class Templates(TestCase): '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), - 'firstof10': ('{% firstof a %}', {'a': '<'}, '<'), # Variables are NOT auto-escaped. + 'firstof10': ('{% firstof a %}', {'a': '<'}, '<'), + + 'firstof11': ('{% load firstof from future %}{% firstof a b %}', {'a': '<', 'b': '>'}, '<'), + 'firstof12': ('{% load firstof from future %}{% firstof a b %}', {'a': '', 'b': '>'}, '>'), + 'firstof13': ('{% load firstof from future %}{% autoescape off %}{% firstof a %}{% endautoescape %}', {'a': '<'}, '<'), + 'firstof14': ('{% load firstof from future %}{% firstof a|safe b %}', {'a': '<'}, '<'), ### FOR TAG ############################################################### 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"), @@ -827,6 +892,8 @@ class Templates(TestCase): '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"), + # Ticket 19882 + 'for-tag-filter-ws': ("{% load custom %}{% for x in s|noop:'x y' %}{{ x }}{% endfor %}", {'s': 'abc'}, 'abc'), ### IF TAG ################################################################ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"), @@ -995,6 +1062,9 @@ class Templates(TestCase): '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'), + # Test whitespace in filter arguments + 'ifchanged-filter-ws': ('{% load custom %}{% for n in num %}{% ifchanged n|noop:"x y" %}..{% endifchanged %}{{ n }}{% endfor %}', {'num': (1,2,3)}, '..1..2..3'), + ### IFEQUAL TAG ########################################################### 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""), 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"), @@ -1327,7 +1397,10 @@ class Templates(TestCase): 'i18n33': ('{% load i18n %}{{ langcode|language_name }} {{ langcode|language_name_local }} {{ langcode|language_bidi }}', {'langcode': 'nl'}, 'Dutch Nederlands False'), # blocktrans handling of variables which are not in the context. - 'i18n34': ('{% load i18n %}{% blocktrans %}{{ missing }}{% endblocktrans %}', {}, ''), + # this should work as if blocktrans was not there (bug #19915) + 'i18n34': ('{% load i18n %}{% blocktrans %}{{ missing }}{% endblocktrans %}', {}, ('', 'INVALID')), + 'i18n34_2': ("{% load i18n %}{% blocktrans with a='α' %}{{ missing }}{% endblocktrans %}", {}, ('', 'INVALID')), + 'i18n34_3': ('{% load i18n %}{% blocktrans with a=anton %}{{ missing }}{% endblocktrans %}', {'anton': 'α'}, ('', 'INVALID')), # trans tag with as var 'i18n35': ('{% load i18n %}{% trans "Page not found" as page_not_found %}{{ page_not_found }}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"), @@ -1335,6 +1408,10 @@ class Templates(TestCase): 'i18n36': ('{% load i18n %}{% trans "Page not found" as page_not_found noop %}{{ page_not_found }}', {'LANGUAGE_CODE': 'de'}, "Page not found"), 'i18n37': ('{% load i18n %}{% trans "Page not found" as page_not_found %}{% blocktrans %}Error: {{ page_not_found }}{% endblocktrans %}', {'LANGUAGE_CODE': 'de'}, "Error: Seite nicht gefunden"), + # Test whitespace in filter arguments + 'i18n38': ('{% load i18n custom %}{% get_language_info for "de"|noop:"x y" as l %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {}, 'de: German/Deutsch bidi=False'), + 'i18n38_2': ('{% load i18n custom %}{% get_language_info_list for langcodes|noop:"x y" as langs %}{% for l in langs %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': ['it', 'no']}, 'it: Italian/italiano bidi=False; no: Norwegian/norsk bidi=False; '), + ### HANDLING OF TEMPLATE_STRING_IF_INVALID ################################### 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')), @@ -1416,6 +1493,16 @@ class Templates(TestCase): {'foo': 'z', 'bar': ['a', 'd']}]}, 'abc:xy,ad:z,'), + # Test syntax + 'regroup05': ('{% regroup data by bar as %}', {}, + template.TemplateSyntaxError), + 'regroup06': ('{% regroup data by bar thisaintright grouped %}', {}, + template.TemplateSyntaxError), + 'regroup07': ('{% regroup data thisaintright bar as grouped %}', {}, + template.TemplateSyntaxError), + 'regroup08': ('{% regroup data by bar as grouped toomanyargs %}', {}, + template.TemplateSyntaxError), + ### SSI TAG ######################################################## # Test normal behavior @@ -1484,6 +1571,9 @@ class Templates(TestCase): 'widthratio14a': ('{% widthratio a b c %}', {'a':0,'b':100,'c':'c'}, template.TemplateSyntaxError), 'widthratio14b': ('{% widthratio a b c %}', {'a':0,'b':100,'c':None}, template.TemplateSyntaxError), + # Test whitespace in filter argument + 'widthratio15': ('{% load custom %}{% widthratio a|noop:"x y" b 0 %}', {'a':50,'b':100}, '0'), + ### WITH TAG ######################################################## 'with01': ('{% with key=dict.key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'), 'legacywith01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'), @@ -1513,33 +1603,33 @@ class Templates(TestCase): ### URL TAG ######################################################## # Successes - '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/'), + 'url01': ('{% url "template_tests.views.client" client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), + 'url02': ('{% url "template_tests.views.client_action" id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), + 'url02a': ('{% url "template_tests.views.client_action" client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), + 'url02b': ("{% url 'template_tests.views.client_action' id=client.id action='update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'), + 'url02c': ("{% url 'template_tests.views.client_action' client.id 'update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'), + 'url03': ('{% url "template_tests.views.index" %}', {}, '/url_tag/'), 'url04': ('{% url "named.client" client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), 'url05': ('{% url "метка_оператора" v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'url06': ('{% url "метка_оператора_2" tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), - 'url07': ('{% url "regressiontests.templates.views.client2" tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), + 'url07': ('{% url "template_tests.views.client2" tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'url08': ('{% url "метка_оператора" v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'url09': ('{% 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/'), + 'url10': ('{% url "template_tests.views.client_action" id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'), + 'url11': ('{% url "template_tests.views.client_action" id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/%3D%3D/'), + 'url12': ('{% url "template_tests.views.client_action" id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/%2C/'), + 'url13': ('{% url "template_tests.views.client_action" id=client.id action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'), + 'url14': ('{% url "template_tests.views.client_action" client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'), + 'url15': ('{% url "template_tests.views.client_action" 12 "test" %}', {}, '/url_tag/client/12/test/'), + 'url18': ('{% url "template_tests.views.client" "1,2" %}', {}, '/url_tag/client/1%2C2/'), - 'url19': ('{% url named_url client.id %}', {'named_url': 'regressiontests.templates.views.client', 'client': {'id': 1}}, '/url_tag/client/1/'), + 'url19': ('{% url named_url client.id %}', {'named_url': 'template_tests.views.client', 'client': {'id': 1}}, '/url_tag/client/1/'), 'url20': ('{% url url_name_in_var client.id %}', {'url_name_in_var': 'named.client', 'client': {'id': 1}}, '/url_tag/named-client/1/'), # Failures 'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError), 'url-fail02': ('{% url "no_such_view" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)), - 'url-fail03': ('{% url "regressiontests.templates.views.client" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)), + 'url-fail03': ('{% url "template_tests.views.client" %}', {}, (urlresolvers.NoReverseMatch, 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), @@ -1549,7 +1639,7 @@ class Templates(TestCase): 'url-fail11': ('{% url named_url %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)), 'url-fail12': ('{% url named_url %}', {'named_url': 'no_such_view'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)), - 'url-fail13': ('{% url named_url %}', {'named_url': 'regressiontests.templates.views.client'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)), + 'url-fail13': ('{% url named_url %}', {'named_url': 'template_tests.views.client'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)), 'url-fail14': ('{% url named_url id, %}', {'named_url': 'view'}, template.TemplateSyntaxError), 'url-fail15': ('{% url named_url id= %}', {'named_url': 'view'}, template.TemplateSyntaxError), 'url-fail16': ('{% url named_url a.id=id %}', {'named_url': 'view'}, template.TemplateSyntaxError), @@ -1558,8 +1648,8 @@ class Templates(TestCase): 'url-fail19': ('{% url named_url id=", %}', {'named_url': 'view'}, 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-asvar01': ('{% url "template_tests.views.index" as url %}', {}, ''), + 'url-asvar02': ('{% url "template_tests.views.index" as url %}{{ url }}', {}, '/url_tag/'), 'url-asvar03': ('{% url "no_such_view" as url %}{{ url }}', {}, ''), ### CACHE TAG ###################################################### @@ -1585,6 +1675,9 @@ class Templates(TestCase): # 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'), + # Test whitespace in filter arguments + 'cache18': ('{% load cache custom %}{% cache 2|noop:"x y" cache18 %}cache18{% endcache %}', {}, 'cache18'), + ### AUTOESCAPE TAG ############################################## 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"), @@ -1649,6 +1742,17 @@ class Templates(TestCase): 'verbatim-tag05': ('{% verbatim %}{% endverbatim %}{% verbatim %}{% endverbatim %}', {}, ''), 'verbatim-tag06': ("{% verbatim special %}Don't {% endverbatim %} just yet{% endverbatim special %}", {}, "Don't {% endverbatim %} just yet"), } + + if numpy: + tests.update({ + # Numpy's array-index syntax allows a template to access a certain item of a subscriptable object. + 'numpy-array-index01': ("{{ var.1 }}", {"var": numpy.array(["first item", "second item"])}, "second item"), + + # Fail silently when the array index is out of range. + 'numpy-array-index02': ("{{ var.5 }}", {"var": numpy.array(["first item", "second item"])}, ("", "INVALID")), + }) + + return tests class TemplateTagLoading(unittest.TestCase): diff --git a/tests/regressiontests/templates/urls.py b/tests/template_tests/urls.py index fe7f9c1f30..b5498fade1 100644 --- a/tests/regressiontests/templates/urls.py +++ b/tests/template_tests/urls.py @@ -16,5 +16,5 @@ urlpatterns = patterns('', # Unicode strings are permitted everywhere. url(r'^Юникод/(\w+)/$', views.client2, name="метка_оператора"), - url(r'^Юникод/(?P<tag>\S+)/$', 'regressiontests.templates.views.client2', name="метка_оператора_2"), + url(r'^Юникод/(?P<tag>\S+)/$', 'template_tests.views.client2', name="метка_оператора_2"), ) diff --git a/tests/regressiontests/templates/views.py b/tests/template_tests/views.py index ed15893239..ed15893239 100644 --- a/tests/regressiontests/templates/views.py +++ b/tests/template_tests/views.py diff --git a/tests/regressiontests/string_lookup/__init__.py b/tests/test_client/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/string_lookup/__init__.py +++ b/tests/test_client/__init__.py diff --git a/tests/modeltests/test_client/fixtures/testdata.json b/tests/test_client/fixtures/testdata.json index 0dcf625939..0dcf625939 100644 --- a/tests/modeltests/test_client/fixtures/testdata.json +++ b/tests/test_client/fixtures/testdata.json diff --git a/tests/regressiontests/text/models.py b/tests/test_client/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/text/models.py +++ b/tests/test_client/models.py diff --git a/tests/modeltests/test_client/models.py b/tests/test_client/tests.py index 0f3cba7e88..0f3cba7e88 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/test_client/tests.py diff --git a/tests/modeltests/test_client/urls.py b/tests/test_client/urls.py index 67c475eaff..67c475eaff 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/test_client/urls.py diff --git a/tests/modeltests/test_client/views.py b/tests/test_client/views.py index 477a27b178..f760466497 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/test_client/views.py @@ -154,8 +154,8 @@ def _permission_protected_view(request): name='Permissions Template') c = Context({'user': request.user}) return HttpResponse(t.render(c)) -permission_protected_view = permission_required('modeltests.test_perm')(_permission_protected_view) -permission_protected_view_exception = permission_required('modeltests.test_perm', raise_exception=True)(_permission_protected_view) +permission_protected_view = permission_required('permission_not_granted')(_permission_protected_view) +permission_protected_view_exception = permission_required('permission_not_granted', raise_exception=True)(_permission_protected_view) class _ViewManager(object): @method_decorator(login_required) @@ -166,7 +166,7 @@ class _ViewManager(object): c = Context({'user': request.user}) return HttpResponse(t.render(c)) - @method_decorator(permission_required('modeltests.test_perm')) + @method_decorator(permission_required('permission_not_granted')) def permission_protected_view(self, request): t = Template('This is a permission protected test using a method. ' 'Username is {{ user.username }}. ' diff --git a/tests/regressiontests/swappable_models/__init__.py b/tests/test_client_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/swappable_models/__init__.py +++ b/tests/test_client_regress/__init__.py diff --git a/tests/regressiontests/test_client_regress/bad_templates/404.html b/tests/test_client_regress/bad_templates/404.html index 816bcb9e4e..816bcb9e4e 100644 --- a/tests/regressiontests/test_client_regress/bad_templates/404.html +++ b/tests/test_client_regress/bad_templates/404.html diff --git a/tests/regressiontests/test_client_regress/fixtures/testdata.json b/tests/test_client_regress/fixtures/testdata.json index 0dcf625939..0dcf625939 100644 --- a/tests/regressiontests/test_client_regress/fixtures/testdata.json +++ b/tests/test_client_regress/fixtures/testdata.json diff --git a/tests/regressiontests/urlpatterns_reverse/models.py b/tests/test_client_regress/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/urlpatterns_reverse/models.py +++ b/tests/test_client_regress/models.py diff --git a/tests/regressiontests/test_client_regress/session.py b/tests/test_client_regress/session.py index 665729cf00..665729cf00 100644 --- a/tests/regressiontests/test_client_regress/session.py +++ b/tests/test_client_regress/session.py diff --git a/tests/regressiontests/test_client_regress/templates/request_context.html b/tests/test_client_regress/templates/request_context.html index 37a2425c09..37a2425c09 100644 --- a/tests/regressiontests/test_client_regress/templates/request_context.html +++ b/tests/test_client_regress/templates/request_context.html diff --git a/tests/regressiontests/test_client_regress/templates/unicode.html b/tests/test_client_regress/templates/unicode.html index bdb6c91a8f..bdb6c91a8f 100644 --- a/tests/regressiontests/test_client_regress/templates/unicode.html +++ b/tests/test_client_regress/templates/unicode.html diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index 5ba5d3c4b3..2582b210c4 100644 --- a/tests/regressiontests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -131,6 +131,18 @@ class AssertContainsTests(TestCase): self.assertNotContains(r, 'はたけ') self.assertNotContains(r, b'\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8')) + def test_binary_contains(self): + r = self.client.get('/test_client_regress/check_binary/') + self.assertContains(r, b'%PDF-1.4\r\n%\x93\x8c\x8b\x9e') + with self.assertRaises(AssertionError): + self.assertContains(r, b'%PDF-1.4\r\n%\x93\x8c\x8b\x9e', count=2) + + def test_binary_not_contains(self): + r = self.client.get('/test_client_regress/check_binary/') + self.assertNotContains(r, b'%ODF-1.4\r\n%\x93\x8c\x8b\x9e') + with self.assertRaises(AssertionError): + self.assertNotContains(r, b'%PDF-1.4\r\n%\x93\x8c\x8b\x9e') + def test_nontext_contains(self): r = self.client.get('/test_client_regress/no_template_view/') self.assertContains(r, ugettext_lazy('once')) @@ -554,7 +566,7 @@ class LoginTests(TestCase): @override_settings( PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), - SESSION_ENGINE='regressiontests.test_client_regress.session' + SESSION_ENGINE='test_client_regress.session' ) class SessionEngineTests(TestCase): fixtures = ['testdata'] @@ -644,7 +656,7 @@ class TemplateExceptionTests(TestCase): # 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' + urls = 'test_client_regress.urls' def test_urlconf_was_changed(self): "TestCase can enforce a custom URLconf on a per-test basis" @@ -783,6 +795,13 @@ class RequestMethodTests(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'request method: DELETE') + def test_patch(self): + "Request a view via request method PATCH" + response = self.client.patch('/test_client_regress/request_methods/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: PATCH') + + class RequestMethodStringDataTests(TestCase): def test_post(self): "Request a view with string data via request method POST" @@ -800,6 +819,14 @@ class RequestMethodStringDataTests(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'request method: PUT') + def test_patch(self): + "Request a view with string data via request method PATCH" + # Regression test for #17797 + data = '{"test": "json"}' + response = self.client.patch('/test_client_regress/request_methods/', data=data, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'request method: PATCH') + class QueryStringTests(TestCase): def test_get_like_requests(self): # See: https://code.djangoproject.com/ticket/10571. diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/test_client_regress/urls.py index d869c234eb..1332537d57 100644 --- a/tests/regressiontests/test_client_regress/urls.py +++ b/tests/test_client_regress/urls.py @@ -28,6 +28,7 @@ urlpatterns = patterns('', (r'^check_session/$', views.check_session_view), (r'^request_methods/$', views.request_methods_view), (r'^check_unicode/$', views.return_unicode), + (r'^check_binary/$', views.return_undecodable_binary), (r'^parse_unicode_json/$', views.return_json_file), (r'^check_headers/$', views.check_headers), (r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/check_headers/')), diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/test_client_regress/views.py index 9b0654806b..7e86ffd8ca 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/test_client_regress/views.py @@ -70,6 +70,11 @@ def request_methods_view(request): def return_unicode(request): return render_to_response('unicode.html') +def return_undecodable_binary(request): + return HttpResponse( + b'%PDF-1.4\r\n%\x93\x8c\x8b\x9e ReportLab Generated PDF document http://www.reportlab.com' + ) + 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']) diff --git a/tests/regressiontests/syndication/__init__.py b/tests/test_runner/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/syndication/__init__.py +++ b/tests/test_runner/__init__.py diff --git a/tests/regressiontests/templates/__init__.py b/tests/test_runner/deprecation_app/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/templates/__init__.py +++ b/tests/test_runner/deprecation_app/__init__.py diff --git a/tests/test_runner/deprecation_app/models.py b/tests/test_runner/deprecation_app/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/tests/test_runner/deprecation_app/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/tests/test_runner/deprecation_app/tests.py b/tests/test_runner/deprecation_app/tests.py new file mode 100644 index 0000000000..6dee0888ec --- /dev/null +++ b/tests/test_runner/deprecation_app/tests.py @@ -0,0 +1,11 @@ +import warnings + +from django.test import TestCase + +warnings.warn("module-level warning from deprecation_app", DeprecationWarning) + +class DummyTest(TestCase): + def test_warn(self): + warnings.warn("warning from test", DeprecationWarning) + + diff --git a/tests/regressiontests/test_runner/invalid_app/__init__.py b/tests/test_runner/invalid_app/__init__.py index 45efd37d3e..45efd37d3e 100644 --- a/tests/regressiontests/test_runner/invalid_app/__init__.py +++ b/tests/test_runner/invalid_app/__init__.py diff --git a/tests/regressiontests/templates/templatetags/__init__.py b/tests/test_runner/invalid_app/models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/templates/templatetags/__init__.py +++ b/tests/test_runner/invalid_app/models/__init__.py diff --git a/tests/regressiontests/test_runner/invalid_app/tests/__init__.py b/tests/test_runner/invalid_app/tests/__init__.py index b4ed71824b..b4ed71824b 100644 --- a/tests/regressiontests/test_runner/invalid_app/tests/__init__.py +++ b/tests/test_runner/invalid_app/tests/__init__.py diff --git a/tests/regressiontests/test_runner/models.py b/tests/test_runner/models.py index 9a072e627c..9a072e627c 100644 --- a/tests/regressiontests/test_runner/models.py +++ b/tests/test_runner/models.py diff --git a/tests/regressiontests/test_runner/tests.py b/tests/test_runner/tests.py index c723f162a4..9f0cc794b7 100644 --- a/tests/regressiontests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -1,8 +1,9 @@ """ Tests for django test runner """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals +import sys from optparse import make_option from django.core.exceptions import ImproperlyConfigured @@ -14,12 +15,12 @@ from django.test.testcases import connections_support_transactions from django.utils import unittest from django.utils.importlib import import_module -from ..admin_scripts.tests import AdminScriptTestCase +from admin_scripts.tests import AdminScriptTestCase from .models import Person -TEST_APP_OK = 'regressiontests.test_runner.valid_app.models' -TEST_APP_ERROR = 'regressiontests.test_runner.invalid_app.models' +TEST_APP_OK = 'test_runner.valid_app.models' +TEST_APP_ERROR = 'test_runner.invalid_app.models' class DependencyOrderingTests(unittest.TestCase): @@ -145,10 +146,15 @@ class ManageCommandTests(unittest.TestCase): def test_custom_test_runner(self): call_command('test', 'sites', - testrunner='regressiontests.test_runner.tests.MockTestRunner') + testrunner='test_runner.tests.MockTestRunner') self.assertTrue(MockTestRunner.invoked, "The custom test runner has not been invoked") + def test_bad_test_runner(self): + with self.assertRaises(AttributeError): + call_command('test', 'sites', + testrunner='test_runner.NonExistentRunner') + class CustomOptionsTestRunner(simple.DjangoTestSuiteRunner): option_list = ( @@ -172,7 +178,7 @@ class CustomTestRunnerOptionsTests(AdminScriptTestCase): def setUp(self): settings = { - 'TEST_RUNNER': '\'regressiontests.test_runner.tests.CustomOptionsTestRunner\'', + 'TEST_RUNNER': '\'test_runner.tests.CustomOptionsTestRunner\'', } self.write_settings('settings.py', sdict=settings) @@ -180,25 +186,26 @@ class CustomTestRunnerOptionsTests(AdminScriptTestCase): self.remove_settings('settings.py') def test_default_options(self): - args = ['test', '--settings=regressiontests.settings'] + args = ['test', '--settings=test_project.settings'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, '1:2:3') def test_default_and_given_options(self): - args = ['test', '--settings=regressiontests.settings', '--option_b=foo'] + args = ['test', '--settings=test_project.settings', '--option_b=foo'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, '1:foo:3') def test_option_name_and_value_separated(self): - args = ['test', '--settings=regressiontests.settings', '--option_b', 'foo'] + args = ['test', '--settings=test_project.settings', '--option_b', 'foo'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, '1:foo:3') def test_all_options_given(self): - args = ['test', '--settings=regressiontests.settings', '--option_a=bar', '--option_b=foo', '--option_c=31337'] + args = ['test', '--settings=test_project.settings', '--option_a=bar', + '--option_b=foo', '--option_c=31337'] out, err = self.run_django_admin(args) self.assertNoOutput(err) self.assertOutput(out, 'bar:foo:31337') @@ -261,6 +268,48 @@ class Sqlite3InMemoryTestDbs(unittest.TestCase): db.connections = old_db_connections +class DummyBackendTest(unittest.TestCase): + def test_setup_databases(self): + """ + Test that setup_databases() doesn't fail with dummy database backend. + """ + runner = DjangoTestSuiteRunner(verbosity=0) + old_db_connections = db.connections + try: + db.connections = db.ConnectionHandler({}) + old_config = runner.setup_databases() + runner.teardown_databases(old_config) + except Exception as e: + self.fail("setup_databases/teardown_databases unexpectedly raised " + "an error: %s" % e) + finally: + db.connections = old_db_connections + + +class DeprecationDisplayTest(AdminScriptTestCase): + # tests for 19546 + def setUp(self): + settings = {'INSTALLED_APPS': '("test_runner.deprecation_app",)', + 'DATABASES': '{"default": {"ENGINE":"django.db.backends.sqlite3", "NAME":":memory:"}}' } + self.write_settings('settings.py', sdict=settings) + + def tearDown(self): + self.remove_settings('settings.py') + + def test_runner_deprecation_verbosity_default(self): + args = ['test', '--settings=test_project.settings'] + out, err = self.run_django_admin(args) + self.assertIn("DeprecationWarning: warning from test", err) + self.assertIn("DeprecationWarning: module-level warning from deprecation_app", err) + + @unittest.skipIf(sys.version_info[:2] == (2, 6), + "On Python 2.6, DeprecationWarnings are visible anyway") + def test_runner_deprecation_verbosity_zero(self): + args = ['test', '--settings=settings', '--verbosity=0'] + out, err = self.run_django_admin(args) + self.assertFalse("DeprecationWarning: warning from test" in err) + + class AutoIncrementResetTest(TransactionTestCase): """ Here we test creating the same model two times in different test methods, diff --git a/tests/regressiontests/test_runner/valid_app/__init__.py b/tests/test_runner/valid_app/__init__.py index 8d60a80c53..8d60a80c53 100644 --- a/tests/regressiontests/test_runner/valid_app/__init__.py +++ b/tests/test_runner/valid_app/__init__.py diff --git a/tests/regressiontests/templates/templatetags/subpackage/__init__.py b/tests/test_runner/valid_app/models/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/templates/templatetags/subpackage/__init__.py +++ b/tests/test_runner/valid_app/models/__init__.py diff --git a/tests/regressiontests/test_client_regress/__init__.py b/tests/test_runner/valid_app/tests/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_client_regress/__init__.py +++ b/tests/test_runner/valid_app/tests/__init__.py diff --git a/tests/test_sqlite.py b/tests/test_sqlite.py index c04ef97cd0..6bce452c79 100644 --- a/tests/test_sqlite.py +++ b/tests/test_sqlite.py @@ -22,9 +22,8 @@ DATABASES = { } SECRET_KEY = "django_tests_secret_key" -# To speed up tests under SQLite we use the MD5 hasher as the default one. -# This should not be needed under other databases, as the relative speedup -# is only marginal there. + +# Use a fast hasher to speed up tests. PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', ) diff --git a/tests/regressiontests/test_runner/__init__.py b/tests/test_suite_override/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_runner/__init__.py +++ b/tests/test_suite_override/__init__.py diff --git a/tests/regressiontests/version/models.py b/tests/test_suite_override/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/version/models.py +++ b/tests/test_suite_override/models.py diff --git a/tests/test_suite_override/tests.py b/tests/test_suite_override/tests.py new file mode 100644 index 0000000000..dea110434f --- /dev/null +++ b/tests/test_suite_override/tests.py @@ -0,0 +1,34 @@ +from django.db.models import get_app +from django.test.simple import build_suite +from django.utils import unittest + + +def suite(): + testSuite = unittest.TestSuite() + testSuite.addTest(SuiteOverrideTest('test_suite_override')) + return testSuite + + +class SuiteOverrideTest(unittest.TestCase): + def test_suite_override(self): + """ + Validate that you can define a custom suite when running tests with + ``django.test.simple.DjangoTestSuiteRunner`` (which builds up a test + suite using ``build_suite``). + """ + + app = get_app("test_suite_override") + suite = build_suite(app) + self.assertEqual(suite.countTestCases(), 1) + + +class SampleTests(unittest.TestCase): + """These tests should not be discovered, due to the custom suite.""" + def test_one(self): + pass + + def test_two(self): + pass + + def test_three(self): + pass diff --git a/tests/regressiontests/test_runner/invalid_app/models/__init__.py b/tests/test_utils/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_runner/invalid_app/models/__init__.py +++ b/tests/test_utils/__init__.py diff --git a/tests/test_utils/doctest_output.py b/tests/test_utils/doctest_output.py new file mode 100644 index 0000000000..724364428a --- /dev/null +++ b/tests/test_utils/doctest_output.py @@ -0,0 +1,77 @@ +from django.utils import six + +__test__ = {"API_TEST": r""" +# Some checks of the doctest output normalizer. +# Standard doctests do fairly +>>> import json +>>> from django.utils.xmlutils import SimplerXMLGenerator +>>> from django.utils.six import StringIO + +>>> def produce_json(): +... return json.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() + +# 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>' + +"""} + +if not six.PY3: + __test__["API_TEST"] += """ +>>> def produce_long(): +... return 42L + +>>> def produce_int(): +... return 42 + +# Long values are normalized and are comparable to normal integers ... +>>> produce_long() +42 + +# ... and vice versa +>>> produce_int() +42L +""" diff --git a/tests/regressiontests/test_utils/fixtures/should_not_be_loaded.json b/tests/test_utils/fixtures/should_not_be_loaded.json index 9f4df38996..9f4df38996 100644 --- a/tests/regressiontests/test_utils/fixtures/should_not_be_loaded.json +++ b/tests/test_utils/fixtures/should_not_be_loaded.json diff --git a/tests/regressiontests/test_utils/models.py b/tests/test_utils/models.py index 85a1031c02..85a1031c02 100644 --- a/tests/regressiontests/test_utils/models.py +++ b/tests/test_utils/models.py diff --git a/tests/regressiontests/test_utils/templates/template_used/alternative.html b/tests/test_utils/templates/template_used/alternative.html index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_utils/templates/template_used/alternative.html +++ b/tests/test_utils/templates/template_used/alternative.html diff --git a/tests/regressiontests/test_utils/templates/template_used/base.html b/tests/test_utils/templates/template_used/base.html index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_utils/templates/template_used/base.html +++ b/tests/test_utils/templates/template_used/base.html diff --git a/tests/regressiontests/test_utils/templates/template_used/extends.html b/tests/test_utils/templates/template_used/extends.html index d14bfa2779..d14bfa2779 100644 --- a/tests/regressiontests/test_utils/templates/template_used/extends.html +++ b/tests/test_utils/templates/template_used/extends.html diff --git a/tests/regressiontests/test_utils/templates/template_used/include.html b/tests/test_utils/templates/template_used/include.html index 2d6c954f38..2d6c954f38 100644 --- a/tests/regressiontests/test_utils/templates/template_used/include.html +++ b/tests/test_utils/templates/template_used/include.html diff --git a/tests/regressiontests/test_utils/tests.py b/tests/test_utils/tests.py index d5d49b2104..d72a482819 100644 --- a/tests/regressiontests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1,11 +1,17 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals +import warnings +from django.db import connection from django.forms import EmailField, IntegerField from django.http import HttpResponse from django.template.loader import render_to_string from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature +from django.test.html import HTMLParseError, parse_html +from django.test.simple import make_doctest +from django.test.utils import CaptureQueriesContext from django.utils import six +from django.utils import unittest from django.utils.unittest import skip from .models import Person @@ -23,7 +29,7 @@ class SkippingTestCase(TestCase): class AssertNumQueriesTests(TestCase): - urls = 'regressiontests.test_utils.urls' + urls = 'test_utils.urls' def test_assert_num_queries(self): def test_func(): @@ -94,8 +100,62 @@ class AssertQuerysetEqualTests(TestCase): ) +class CaptureQueriesContextManagerTests(TestCase): + urls = 'test_utils.urls' + + def setUp(self): + self.person_pk = six.text_type(Person.objects.create(name='test').pk) + + def test_simple(self): + with CaptureQueriesContext(connection) as captured_queries: + Person.objects.get(pk=self.person_pk) + self.assertEqual(len(captured_queries), 1) + self.assertIn(self.person_pk, captured_queries[0]['sql']) + + with CaptureQueriesContext(connection) as captured_queries: + pass + self.assertEqual(0, len(captured_queries)) + + def test_within(self): + with CaptureQueriesContext(connection) as captured_queries: + Person.objects.get(pk=self.person_pk) + self.assertEqual(len(captured_queries), 1) + self.assertIn(self.person_pk, captured_queries[0]['sql']) + + def test_nested(self): + with CaptureQueriesContext(connection) as captured_queries: + Person.objects.count() + with CaptureQueriesContext(connection) as nested_captured_queries: + Person.objects.count() + self.assertEqual(1, len(nested_captured_queries)) + self.assertEqual(2, len(captured_queries)) + + def test_failure(self): + with self.assertRaises(TypeError): + with CaptureQueriesContext(connection): + raise TypeError + + def test_with_client(self): + with CaptureQueriesContext(connection) as captured_queries: + self.client.get("/test_utils/get_person/%s/" % self.person_pk) + self.assertEqual(len(captured_queries), 1) + self.assertIn(self.person_pk, captured_queries[0]['sql']) + + with CaptureQueriesContext(connection) as captured_queries: + self.client.get("/test_utils/get_person/%s/" % self.person_pk) + self.assertEqual(len(captured_queries), 1) + self.assertIn(self.person_pk, captured_queries[0]['sql']) + + with CaptureQueriesContext(connection) as captured_queries: + self.client.get("/test_utils/get_person/%s/" % self.person_pk) + self.client.get("/test_utils/get_person/%s/" % self.person_pk) + self.assertEqual(len(captured_queries), 2) + self.assertIn(self.person_pk, captured_queries[0]['sql']) + self.assertIn(self.person_pk, captured_queries[1]['sql']) + + class AssertNumQueriesContextManagerTests(TestCase): - urls = 'regressiontests.test_utils.urls' + urls = 'test_utils.urls' def test_simple(self): with self.assertNumQueries(0): @@ -219,30 +279,31 @@ class SaveRestoreWarningState(TestCase): # In reality this test could be satisfied by many broken implementations # of save_warnings_state/restore_warnings_state (e.g. just # warnings.resetwarnings()) , but it is difficult to test more. - import warnings - self.save_warnings_state() + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) - class MyWarning(Warning): - pass + self.save_warnings_state() - # Add a filter that causes an exception to be thrown, so we can catch it - warnings.simplefilter("error", MyWarning) - self.assertRaises(Warning, lambda: warnings.warn("warn", MyWarning)) + class MyWarning(Warning): + pass - # Now restore. - self.restore_warnings_state() - # After restoring, we shouldn't get an exception. But we don't want a - # warning printed either, so we have to silence the warning. - warnings.simplefilter("ignore", MyWarning) - warnings.warn("warn", MyWarning) + # Add a filter that causes an exception to be thrown, so we can catch it + warnings.simplefilter("error", MyWarning) + self.assertRaises(Warning, lambda: warnings.warn("warn", MyWarning)) - # Remove the filter we just added. - self.restore_warnings_state() + # Now restore. + self.restore_warnings_state() + # After restoring, we shouldn't get an exception. But we don't want a + # warning printed either, so we have to silence the warning. + warnings.simplefilter("ignore", MyWarning) + warnings.warn("warn", MyWarning) + + # Remove the filter we just added. + self.restore_warnings_state() class HTMLEqualTests(TestCase): def test_html_parser(self): - from django.test.html import parse_html element = parse_html('<div><p>Hello</p></div>') self.assertEqual(len(element.children), 1) self.assertEqual(element.children[0].name, 'p') @@ -256,7 +317,6 @@ class HTMLEqualTests(TestCase): self.assertEqual(dom[0], 'foo') def test_parse_html_in_script(self): - from django.test.html import parse_html parse_html('<script>var a = "<p" + ">";</script>'); parse_html(''' <script> @@ -272,8 +332,6 @@ class HTMLEqualTests(TestCase): self.assertEqual(dom.children[0], "<p>foo</p> '</scr'+'ipt>' <span>bar</span>") def test_self_closing_tags(self): - from django.test.html import parse_html - self_closing_tags = ('br' , 'hr', 'input', 'img', 'meta', 'spacer', 'link', 'frame', 'base', 'col') for tag in self_closing_tags: @@ -397,7 +455,6 @@ class HTMLEqualTests(TestCase): </html>""") def test_html_contain(self): - from django.test.html import parse_html # equal html contains each other dom1 = parse_html('<p>foo') dom2 = parse_html('<p>foo</p>') @@ -421,7 +478,6 @@ class HTMLEqualTests(TestCase): self.assertTrue(dom1 in dom2) def test_count(self): - from django.test.html import parse_html # equal html contains each other one time dom1 = parse_html('<p>foo') dom2 = parse_html('<p>foo</p>') @@ -456,7 +512,6 @@ class HTMLEqualTests(TestCase): self.assertEqual(dom2.count(dom1), 0) def test_parsing_errors(self): - from django.test.html import HTMLParseError, parse_html with self.assertRaises(AssertionError): self.assertHTMLEqual('<p>', '') with self.assertRaises(AssertionError): @@ -485,7 +540,6 @@ class HTMLEqualTests(TestCase): self.assertContains(response, '<p "whats" that>') def test_unicode_handling(self): - from django.http import HttpResponse response = HttpResponse('<p class="help">Some help text for the title (with unicode ŠĐĆŽćžšđ)</p>') self.assertContains(response, '<p class="help">Some help text for the title (with unicode ŠĐĆŽćžšđ)</p>', html=True) @@ -569,79 +623,10 @@ class AssertFieldOutputTests(SimpleTestCase): } self.assertFieldOutput(MyCustomField, {}, {}, empty_value=None) -__test__ = {"API_TEST": r""" -# Some checks of the doctest output normalizer. -# Standard doctests do fairly ->>> import json ->>> from django.utils.xmlutils import SimplerXMLGenerator ->>> from django.utils.six import StringIO - ->>> def produce_json(): -... return json.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() - -# 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>' - -"""} - -if not six.PY3: - __test__["API_TEST"] += """ ->>> def produce_long(): -... return 42L - ->>> def produce_int(): -... return 42 - -# Long values are normalized and are comparable to normal integers ... ->>> produce_long() -42 -# ... and vice versa ->>> produce_int() -42L +class DoctestNormalizerTest(SimpleTestCase): -""" + def test_normalizer(self): + suite = make_doctest("test_utils.doctest_output") + failures = unittest.TextTestRunner(stream=six.StringIO()).run(suite) + self.assertEqual(failures.failures, []) diff --git a/tests/regressiontests/test_utils/urls.py b/tests/test_utils/urls.py index 31fc5cc7fc..31fc5cc7fc 100644 --- a/tests/regressiontests/test_utils/urls.py +++ b/tests/test_utils/urls.py diff --git a/tests/regressiontests/test_utils/views.py b/tests/test_utils/views.py index 5495488e2c..5495488e2c 100644 --- a/tests/regressiontests/test_utils/views.py +++ b/tests/test_utils/views.py diff --git a/tests/regressiontests/test_runner/valid_app/models/__init__.py b/tests/text/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_runner/valid_app/models/__init__.py +++ b/tests/text/__init__.py diff --git a/tests/regressiontests/wsgi/models.py b/tests/text/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/wsgi/models.py +++ b/tests/text/models.py diff --git a/tests/regressiontests/text/tests.py b/tests/text/tests.py index e61c14d103..c14f3baf82 100644 --- a/tests/regressiontests/text/tests.py +++ b/tests/text/tests.py @@ -2,12 +2,16 @@ from __future__ import unicode_literals from django.test import TestCase -from django.utils.encoding import iri_to_uri +from django.utils.encoding import iri_to_uri, force_text +from django.utils.functional import lazy from django.utils.http import (cookie_date, http_date, urlquote, urlquote_plus, urlunquote, urlunquote_plus) +from django.utils import six from django.utils.text import get_text_list, smart_split from django.utils.translation import override +lazystr = lazy(force_text, six.text_type) + class TextTests(TestCase): """ @@ -25,40 +29,36 @@ class TextTests(TestCase): def test_smart_split(self): - self.assertEqual(list(smart_split(r'''This is "a person" test.''')), - ['This', 'is', '"a person"', 'test.']) - - self.assertEqual(list(smart_split(r'''This is "a person's" test.'''))[2], - '"a person\'s"') - - self.assertEqual(list(smart_split(r'''This is "a person\"s" test.'''))[2], - '"a person\\"s"') - - self.assertEqual(list(smart_split('''"a 'one''')), ['"a', "'one"]) - - self.assertEqual(list(smart_split(r'''all friends' tests'''))[1], - "friends'") - - self.assertEqual(list(smart_split('url search_page words="something else"')), - ['url', 'search_page', 'words="something else"']) - - self.assertEqual(list(smart_split("url search_page words='something else'")), - ['url', 'search_page', "words='something else'"]) - - self.assertEqual(list(smart_split('url search_page words "something else"')), - ['url', 'search_page', 'words', '"something else"']) - - self.assertEqual(list(smart_split('url search_page words-"something else"')), - ['url', 'search_page', 'words-"something else"']) - - self.assertEqual(list(smart_split('url search_page words=hello')), - ['url', 'search_page', 'words=hello']) - - self.assertEqual(list(smart_split('url search_page words="something else')), - ['url', 'search_page', 'words="something', 'else']) - - self.assertEqual(list(smart_split("cut:','|cut:' '")), - ["cut:','|cut:' '"]) + testdata = [ + ('This is "a person" test.', + ['This', 'is', '"a person"', 'test.']), + ('This is "a person\'s" test.', + ['This', 'is', '"a person\'s"', 'test.']), + ('This is "a person\\"s" test.', + ['This', 'is', '"a person\\"s"', 'test.']), + ('"a \'one', + ['"a', "'one"]), + ('all friends\' tests', + ['all', 'friends\'', 'tests']), + ('url search_page words="something else"', + ['url', 'search_page', 'words="something else"']), + ("url search_page words='something else'", + ['url', 'search_page', "words='something else'"]), + ('url search_page words "something else"', + ['url', 'search_page', 'words', '"something else"']), + ('url search_page words-"something else"', + ['url', 'search_page', 'words-"something else"']), + ('url search_page words=hello', + ['url', 'search_page', 'words=hello']), + ('url search_page words="something else', + ['url', 'search_page', 'words="something', 'else']), + ("cut:','|cut:' '", + ["cut:','|cut:' '"]), + (lazystr("a b c d"), # Test for #20231 + ['a', 'b', 'c', 'd']), + ] + for test, expected in testdata: + self.assertEqual(list(smart_split(test)), expected) def test_urlquote(self): self.assertEqual(urlquote('Paris & Orl\xe9ans'), diff --git a/tests/regressiontests/test_runner/valid_app/tests/__init__.py b/tests/timezones/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_runner/valid_app/tests/__init__.py +++ b/tests/timezones/__init__.py diff --git a/tests/modeltests/timezones/admin.py b/tests/timezones/admin.py index 4c199813e2..4c199813e2 100644 --- a/tests/modeltests/timezones/admin.py +++ b/tests/timezones/admin.py diff --git a/tests/modeltests/timezones/fixtures/tz_users.xml b/tests/timezones/fixtures/tz_users.xml index 4311ef4c5f..4311ef4c5f 100644 --- a/tests/modeltests/timezones/fixtures/tz_users.xml +++ b/tests/timezones/fixtures/tz_users.xml diff --git a/tests/modeltests/timezones/forms.py b/tests/timezones/forms.py index 3c9c31167e..3c9c31167e 100644 --- a/tests/modeltests/timezones/forms.py +++ b/tests/timezones/forms.py diff --git a/tests/modeltests/timezones/models.py b/tests/timezones/models.py index c49e42f887..c49e42f887 100644 --- a/tests/modeltests/timezones/models.py +++ b/tests/timezones/models.py diff --git a/tests/modeltests/timezones/tests.py b/tests/timezones/tests.py index 2fdf6733a0..dce5c3041d 100644 --- a/tests/modeltests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import datetime import os +import re import sys import time import warnings @@ -188,13 +189,16 @@ class LegacyDatabaseTests(TestCase): self.assertEqual(Event.objects.filter(dt__gte=dt2).count(), 1) self.assertEqual(Event.objects.filter(dt__gt=dt2).count(), 0) - def test_query_date_related_filters(self): + def test_query_datetime_lookups(self): Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0)) Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0)) self.assertEqual(Event.objects.filter(dt__year=2011).count(), 2) self.assertEqual(Event.objects.filter(dt__month=1).count(), 2) self.assertEqual(Event.objects.filter(dt__day=1).count(), 2) self.assertEqual(Event.objects.filter(dt__week_day=7).count(), 2) + self.assertEqual(Event.objects.filter(dt__hour=1).count(), 1) + self.assertEqual(Event.objects.filter(dt__minute=30).count(), 2) + self.assertEqual(Event.objects.filter(dt__second=0).count(), 2) def test_query_aggregation(self): # Only min and max make sense for datetimes. @@ -229,15 +233,30 @@ class LegacyDatabaseTests(TestCase): [afternoon_min_dt], transform=lambda d: d.dt) - def test_query_dates(self): + def test_query_datetimes(self): Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0)) Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0)) - self.assertQuerysetEqual(Event.objects.dates('dt', 'year'), - [datetime.datetime(2011, 1, 1)], transform=lambda d: d) - self.assertQuerysetEqual(Event.objects.dates('dt', 'month'), - [datetime.datetime(2011, 1, 1)], transform=lambda d: d) - self.assertQuerysetEqual(Event.objects.dates('dt', 'day'), - [datetime.datetime(2011, 1, 1)], transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'year'), + [datetime.datetime(2011, 1, 1, 0, 0, 0)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'month'), + [datetime.datetime(2011, 1, 1, 0, 0, 0)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'day'), + [datetime.datetime(2011, 1, 1, 0, 0, 0)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'hour'), + [datetime.datetime(2011, 1, 1, 1, 0, 0), + datetime.datetime(2011, 1, 1, 4, 0, 0)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'minute'), + [datetime.datetime(2011, 1, 1, 1, 30, 0), + datetime.datetime(2011, 1, 1, 4, 30, 0)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'second'), + [datetime.datetime(2011, 1, 1, 1, 30, 0), + datetime.datetime(2011, 1, 1, 4, 30, 0)], + transform=lambda d: d) def test_raw_sql(self): # Regression test for #17755 @@ -397,17 +416,32 @@ class NewDatabaseTests(TestCase): msg = str(warning.message) self.assertTrue(msg.startswith("DateTimeField received a naive datetime")) - def test_query_date_related_filters(self): - # These two dates fall in the same day in EAT, but in different days, - # years and months in UTC, and aggregation is performed in UTC when - # time zone support is enabled. This test could be changed if the - # implementation is changed to perform the aggregation is local time. + @skipUnlessDBFeature('has_zoneinfo_database') + def test_query_datetime_lookups(self): Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT)) Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)) - self.assertEqual(Event.objects.filter(dt__year=2011).count(), 1) - self.assertEqual(Event.objects.filter(dt__month=1).count(), 1) - self.assertEqual(Event.objects.filter(dt__day=1).count(), 1) - self.assertEqual(Event.objects.filter(dt__week_day=7).count(), 1) + self.assertEqual(Event.objects.filter(dt__year=2011).count(), 2) + self.assertEqual(Event.objects.filter(dt__month=1).count(), 2) + self.assertEqual(Event.objects.filter(dt__day=1).count(), 2) + self.assertEqual(Event.objects.filter(dt__week_day=7).count(), 2) + self.assertEqual(Event.objects.filter(dt__hour=1).count(), 1) + self.assertEqual(Event.objects.filter(dt__minute=30).count(), 2) + self.assertEqual(Event.objects.filter(dt__second=0).count(), 2) + + @skipUnlessDBFeature('has_zoneinfo_database') + def test_query_datetime_lookups_in_other_timezone(self): + Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT)) + Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)) + with timezone.override(UTC): + # These two dates fall in the same day in EAT, but in different days, + # years and months in UTC. + self.assertEqual(Event.objects.filter(dt__year=2011).count(), 1) + self.assertEqual(Event.objects.filter(dt__month=1).count(), 1) + self.assertEqual(Event.objects.filter(dt__day=1).count(), 1) + self.assertEqual(Event.objects.filter(dt__week_day=7).count(), 1) + self.assertEqual(Event.objects.filter(dt__hour=22).count(), 1) + self.assertEqual(Event.objects.filter(dt__minute=30).count(), 2) + self.assertEqual(Event.objects.filter(dt__second=0).count(), 2) def test_query_aggregation(self): # Only min and max make sense for datetimes. @@ -442,22 +476,61 @@ class NewDatabaseTests(TestCase): [afternoon_min_dt], transform=lambda d: d.dt) - def test_query_dates(self): - # Same comment as in test_query_date_related_filters. + @skipUnlessDBFeature('has_zoneinfo_database') + def test_query_datetimes(self): Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT)) Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)) - self.assertQuerysetEqual(Event.objects.dates('dt', 'year'), - [datetime.datetime(2010, 1, 1, tzinfo=UTC), - datetime.datetime(2011, 1, 1, tzinfo=UTC)], + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'year'), + [datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=EAT)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'month'), + [datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=EAT)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'day'), + [datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=EAT)], transform=lambda d: d) - self.assertQuerysetEqual(Event.objects.dates('dt', 'month'), - [datetime.datetime(2010, 12, 1, tzinfo=UTC), - datetime.datetime(2011, 1, 1, tzinfo=UTC)], + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'hour'), + [datetime.datetime(2011, 1, 1, 1, 0, 0, tzinfo=EAT), + datetime.datetime(2011, 1, 1, 4, 0, 0, tzinfo=EAT)], transform=lambda d: d) - self.assertQuerysetEqual(Event.objects.dates('dt', 'day'), - [datetime.datetime(2010, 12, 31, tzinfo=UTC), - datetime.datetime(2011, 1, 1, tzinfo=UTC)], + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'minute'), + [datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT), + datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)], transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'second'), + [datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT), + datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)], + transform=lambda d: d) + + @skipUnlessDBFeature('has_zoneinfo_database') + def test_query_datetimes_in_other_timezone(self): + Event.objects.create(dt=datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=EAT)) + Event.objects.create(dt=datetime.datetime(2011, 1, 1, 4, 30, 0, tzinfo=EAT)) + with timezone.override(UTC): + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'year'), + [datetime.datetime(2010, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=UTC)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'month'), + [datetime.datetime(2010, 12, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=UTC)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'day'), + [datetime.datetime(2010, 12, 31, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2011, 1, 1, 0, 0, 0, tzinfo=UTC)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'hour'), + [datetime.datetime(2010, 12, 31, 22, 0, 0, tzinfo=UTC), + datetime.datetime(2011, 1, 1, 1, 0, 0, tzinfo=UTC)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'minute'), + [datetime.datetime(2010, 12, 31, 22, 30, 0, tzinfo=UTC), + datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=UTC)], + transform=lambda d: d) + self.assertQuerysetEqual(Event.objects.datetimes('dt', 'second'), + [datetime.datetime(2010, 12, 31, 22, 30, 0, tzinfo=UTC), + datetime.datetime(2011, 1, 1, 1, 30, 0, tzinfo=UTC)], + transform=lambda d: d) def test_raw_sql(self): # Regression test for #17755 @@ -504,7 +577,9 @@ class SerializationTests(TestCase): self.assertXMLEqual(field.childNodes[0].wholeText, dt) def assert_yaml_contains_datetime(self, yaml, dt): - self.assertIn("- fields: {dt: !!timestamp '%s'}" % dt, yaml) + # Depending on the yaml dumper, '!timestamp' might be absent + six.assertRegex(self, yaml, + r"- fields: {dt: !(!timestamp)? '%s'}" % re.escape(dt)) def test_naive_datetime(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30) @@ -972,7 +1047,7 @@ class NewFormsTests(TestCase): PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminTests(TestCase): - urls = 'modeltests.timezones.urls' + urls = 'timezones.urls' fixtures = ['tz_users.xml'] def setUp(self): diff --git a/tests/modeltests/timezones/urls.py b/tests/timezones/urls.py index e4f42972db..e4f42972db 100644 --- a/tests/modeltests/timezones/urls.py +++ b/tests/timezones/urls.py diff --git a/tests/regressiontests/test_utils/__init__.py b/tests/transactions/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/test_utils/__init__.py +++ b/tests/transactions/__init__.py diff --git a/tests/modeltests/transactions/models.py b/tests/transactions/models.py index 0f8d6b16ec..6c2bcfd23f 100644 --- a/tests/modeltests/transactions/models.py +++ b/tests/transactions/models.py @@ -22,4 +22,4 @@ class Reporter(models.Model): ordering = ('first_name', 'last_name') def __str__(self): - return "%s %s" % (self.first_name, self.last_name) + return ("%s %s" % (self.first_name, self.last_name)).strip() diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py new file mode 100644 index 0000000000..aeb9bc3d2c --- /dev/null +++ b/tests/transactions/tests.py @@ -0,0 +1,635 @@ +from __future__ import absolute_import + +import sys +import warnings + +from django.db import connection, transaction, IntegrityError +from django.test import TransactionTestCase, skipUnlessDBFeature +from django.utils import six +from django.utils.unittest import skipIf, skipUnless + +from .models import Reporter + + +@skipUnless(connection.features.uses_savepoints, + "'atomic' requires transactions and savepoints.") +class AtomicTests(TransactionTestCase): + """ + Tests for the atomic decorator and context manager. + + The tests make assertions on internal attributes because there isn't a + robust way to ask the database for its current transaction state. + + Since the decorator syntax is converted into a context manager (see the + implementation), there are only a few basic tests with the decorator + syntax and the bulk of the tests use the context manager syntax. + """ + + def test_decorator_syntax_commit(self): + @transaction.atomic + def make_reporter(): + Reporter.objects.create(first_name="Tintin") + make_reporter() + self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>']) + + def test_decorator_syntax_rollback(self): + @transaction.atomic + def make_reporter(): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + with six.assertRaisesRegex(self, Exception, "Oops"): + make_reporter() + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_alternate_decorator_syntax_commit(self): + @transaction.atomic() + def make_reporter(): + Reporter.objects.create(first_name="Tintin") + make_reporter() + self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>']) + + def test_alternate_decorator_syntax_rollback(self): + @transaction.atomic() + def make_reporter(): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + with six.assertRaisesRegex(self, Exception, "Oops"): + make_reporter() + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_commit(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>']) + + def test_rollback(self): + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_nested_commit_commit(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + with transaction.atomic(): + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + self.assertQuerysetEqual(Reporter.objects.all(), + ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>']) + + def test_nested_commit_rollback(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>']) + + def test_nested_rollback_commit(self): + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(last_name="Tintin") + with transaction.atomic(): + Reporter.objects.create(last_name="Haddock") + raise Exception("Oops, that's his first name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_nested_rollback_rollback(self): + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(last_name="Tintin") + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + raise Exception("Oops, that's his first name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_merged_commit_commit(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + self.assertQuerysetEqual(Reporter.objects.all(), + ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>']) + + def test_merged_commit_rollback(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + # Writes in the outer block are rolled back too. + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_merged_rollback_commit(self): + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(last_name="Tintin") + with transaction.atomic(savepoint=False): + Reporter.objects.create(last_name="Haddock") + raise Exception("Oops, that's his first name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_merged_rollback_rollback(self): + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(): + Reporter.objects.create(last_name="Tintin") + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + raise Exception("Oops, that's his first name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_reuse_commit_commit(self): + atomic = transaction.atomic() + with atomic: + Reporter.objects.create(first_name="Tintin") + with atomic: + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + self.assertQuerysetEqual(Reporter.objects.all(), + ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>']) + + def test_reuse_commit_rollback(self): + atomic = transaction.atomic() + with atomic: + Reporter.objects.create(first_name="Tintin") + with six.assertRaisesRegex(self, Exception, "Oops"): + with atomic: + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>']) + + def test_reuse_rollback_commit(self): + atomic = transaction.atomic() + with six.assertRaisesRegex(self, Exception, "Oops"): + with atomic: + Reporter.objects.create(last_name="Tintin") + with atomic: + Reporter.objects.create(last_name="Haddock") + raise Exception("Oops, that's his first name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_reuse_rollback_rollback(self): + atomic = transaction.atomic() + with six.assertRaisesRegex(self, Exception, "Oops"): + with atomic: + Reporter.objects.create(last_name="Tintin") + with six.assertRaisesRegex(self, Exception, "Oops"): + with atomic: + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + raise Exception("Oops, that's his first name") + self.assertQuerysetEqual(Reporter.objects.all(), []) + + +class AtomicInsideTransactionTests(AtomicTests): + """All basic tests for atomic should also pass within an existing transaction.""" + + def setUp(self): + self.atomic = transaction.atomic() + self.atomic.__enter__() + + def tearDown(self): + self.atomic.__exit__(*sys.exc_info()) + + +@skipIf(connection.features.autocommits_when_autocommit_is_off, + "This test requires a non-autocommit mode that doesn't autocommit.") +class AtomicWithoutAutocommitTests(AtomicTests): + """All basic tests for atomic should also pass when autocommit is turned off.""" + + def setUp(self): + transaction.set_autocommit(False) + + def tearDown(self): + # The tests access the database after exercising 'atomic', initiating + # a transaction ; a rollback is required before restoring autocommit. + transaction.rollback() + transaction.set_autocommit(True) + + +@skipIf(connection.features.autocommits_when_autocommit_is_off, + "This test requires a non-autocommit mode that doesn't autocommit.") +class AtomicInsideLegacyTransactionManagementTests(AtomicTests): + + def setUp(self): + transaction.enter_transaction_management() + + def tearDown(self): + # The tests access the database after exercising 'atomic', making the + # connection dirty; a rollback is required to make it clean. + transaction.rollback() + transaction.leave_transaction_management() + + +@skipUnless(connection.features.uses_savepoints, + "'atomic' requires transactions and savepoints.") +class AtomicMergeTests(TransactionTestCase): + """Test merging transactions with savepoint=False.""" + + def test_merged_outer_rollback(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Tournesol") + raise Exception("Oops, that's his last name") + # It wasn't possible to roll back + self.assertEqual(Reporter.objects.count(), 3) + # It wasn't possible to roll back + self.assertEqual(Reporter.objects.count(), 3) + # The outer block must roll back + self.assertQuerysetEqual(Reporter.objects.all(), []) + + def test_merged_inner_savepoint_rollback(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + with transaction.atomic(): + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Tournesol") + raise Exception("Oops, that's his last name") + # It wasn't possible to roll back + self.assertEqual(Reporter.objects.count(), 3) + # The first block with a savepoint must roll back + self.assertEqual(Reporter.objects.count(), 1) + self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>']) + + def test_merged_outer_rollback_after_inner_failure_and_inner_success(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Tintin") + # Inner block without a savepoint fails + with six.assertRaisesRegex(self, Exception, "Oops"): + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Haddock") + raise Exception("Oops, that's his last name") + # It wasn't possible to roll back + self.assertEqual(Reporter.objects.count(), 2) + # Inner block with a savepoint succeeds + with transaction.atomic(savepoint=False): + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + # It still wasn't possible to roll back + self.assertEqual(Reporter.objects.count(), 3) + # The outer block must rollback + self.assertQuerysetEqual(Reporter.objects.all(), []) + + +@skipUnless(connection.features.uses_savepoints, + "'atomic' requires transactions and savepoints.") +class AtomicErrorsTests(TransactionTestCase): + + def test_atomic_prevents_setting_autocommit(self): + autocommit = transaction.get_autocommit() + with transaction.atomic(): + with self.assertRaises(transaction.TransactionManagementError): + transaction.set_autocommit(not autocommit) + # Make sure autocommit wasn't changed. + self.assertEqual(connection.autocommit, autocommit) + + def test_atomic_prevents_calling_transaction_methods(self): + with transaction.atomic(): + with self.assertRaises(transaction.TransactionManagementError): + transaction.commit() + with self.assertRaises(transaction.TransactionManagementError): + transaction.rollback() + + def test_atomic_prevents_calling_transaction_management_methods(self): + with transaction.atomic(): + with self.assertRaises(transaction.TransactionManagementError): + transaction.enter_transaction_management() + with self.assertRaises(transaction.TransactionManagementError): + transaction.leave_transaction_management() + + +class AtomicMiscTests(TransactionTestCase): + + def test_wrap_callable_instance(self): + # Regression test for #20028 + class Callable(object): + def __call__(self): + pass + # Must not raise an exception + transaction.atomic(Callable()) + + +class IgnorePendingDeprecationWarningsMixin(object): + + def setUp(self): + super(IgnorePendingDeprecationWarningsMixin, self).setUp() + self.catch_warnings = warnings.catch_warnings() + self.catch_warnings.__enter__() + warnings.filterwarnings("ignore", category=PendingDeprecationWarning) + + def tearDown(self): + self.catch_warnings.__exit__(*sys.exc_info()) + super(IgnorePendingDeprecationWarningsMixin, self).tearDown() + + +class TransactionTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): + + def create_a_reporter_then_fail(self, first, last): + a = Reporter(first_name=first, last_name=last) + a.save() + raise Exception("I meant to do that") + + def remove_a_reporter(self, first_name): + r = Reporter.objects.get(first_name="Alice") + r.delete() + + def manually_managed(self): + r = Reporter(first_name="Dirk", last_name="Gently") + r.save() + transaction.commit() + + def manually_managed_mistake(self): + r = Reporter(first_name="Edward", last_name="Woodward") + r.save() + # Oops, I forgot to commit/rollback! + + @skipUnlessDBFeature('supports_transactions') + def test_autocommit(self): + """ + The default behavior is to autocommit after each save() action. + """ + self.assertRaises(Exception, + self.create_a_reporter_then_fail, + "Alice", "Smith" + ) + + # The object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_autocommit_decorator(self): + """ + The autocommit decorator works exactly the same as the default behavior. + """ + autocomitted_create_then_fail = transaction.autocommit( + self.create_a_reporter_then_fail + ) + self.assertRaises(Exception, + autocomitted_create_then_fail, + "Alice", "Smith" + ) + # Again, the object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_autocommit_decorator_with_using(self): + """ + The autocommit decorator also works with a using argument. + """ + autocomitted_create_then_fail = transaction.autocommit(using='default')( + self.create_a_reporter_then_fail + ) + self.assertRaises(Exception, + autocomitted_create_then_fail, + "Alice", "Smith" + ) + # Again, the object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success(self): + """ + With the commit_on_success decorator, the transaction is only committed + if the function doesn't throw an exception. + """ + committed_on_success = transaction.commit_on_success( + self.create_a_reporter_then_fail) + self.assertRaises(Exception, committed_on_success, "Dirk", "Gently") + # This time the object never got saved + self.assertEqual(Reporter.objects.count(), 0) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success_with_using(self): + """ + The commit_on_success decorator also works with a using argument. + """ + using_committed_on_success = transaction.commit_on_success(using='default')( + self.create_a_reporter_then_fail + ) + self.assertRaises(Exception, + using_committed_on_success, + "Dirk", "Gently" + ) + # This time the object never got saved + self.assertEqual(Reporter.objects.count(), 0) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success_succeed(self): + """ + If there aren't any exceptions, the data will get saved. + """ + Reporter.objects.create(first_name="Alice", last_name="Smith") + remove_comitted_on_success = transaction.commit_on_success( + self.remove_a_reporter + ) + remove_comitted_on_success("Alice") + self.assertEqual(list(Reporter.objects.all()), []) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success_exit(self): + @transaction.autocommit() + def gen_reporter(): + @transaction.commit_on_success + def create_reporter(): + Reporter.objects.create(first_name="Bobby", last_name="Tables") + + create_reporter() + # Much more formal + r = Reporter.objects.get() + r.first_name = "Robert" + r.save() + + gen_reporter() + r = Reporter.objects.get() + self.assertEqual(r.first_name, "Robert") + + + @skipUnlessDBFeature('supports_transactions') + def test_manually_managed(self): + """ + You can manually manage transactions if you really want to, but you + have to remember to commit/rollback. + """ + manually_managed = transaction.commit_manually(self.manually_managed) + manually_managed() + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_manually_managed_mistake(self): + """ + If you forget, you'll get bad errors. + """ + manually_managed_mistake = transaction.commit_manually( + self.manually_managed_mistake + ) + self.assertRaises(transaction.TransactionManagementError, + manually_managed_mistake) + + @skipUnlessDBFeature('supports_transactions') + def test_manually_managed_with_using(self): + """ + The commit_manually function also works with a using argument. + """ + using_manually_managed_mistake = transaction.commit_manually(using='default')( + self.manually_managed_mistake + ) + self.assertRaises(transaction.TransactionManagementError, + using_manually_managed_mistake + ) + + +class TransactionRollbackTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): + def execute_bad_sql(self): + cursor = connection.cursor() + cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") + + @skipUnlessDBFeature('requires_rollback_on_dirty_transaction') + def test_bad_sql(self): + """ + Regression for #11900: If a function wrapped by commit_on_success + writes a transaction that can't be committed, that transaction should + be rolled back. The bug is only visible using the psycopg2 backend, + though the fix is generally a good idea. + """ + execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql) + self.assertRaises(IntegrityError, execute_bad_sql) + transaction.rollback() + +class TransactionContextManagerTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): + def create_reporter_and_fail(self): + Reporter.objects.create(first_name="Bob", last_name="Holtzman") + raise Exception + + @skipUnlessDBFeature('supports_transactions') + def test_autocommit(self): + """ + The default behavior is to autocommit after each save() action. + """ + with self.assertRaises(Exception): + self.create_reporter_and_fail() + # The object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_autocommit_context_manager(self): + """ + The autocommit context manager works exactly the same as the default + behavior. + """ + with self.assertRaises(Exception): + with transaction.autocommit(): + self.create_reporter_and_fail() + + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_autocommit_context_manager_with_using(self): + """ + The autocommit context manager also works with a using argument. + """ + with self.assertRaises(Exception): + with transaction.autocommit(using="default"): + self.create_reporter_and_fail() + + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success(self): + """ + With the commit_on_success context manager, the transaction is only + committed if the block doesn't throw an exception. + """ + with self.assertRaises(Exception): + with transaction.commit_on_success(): + self.create_reporter_and_fail() + + self.assertEqual(Reporter.objects.count(), 0) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success_with_using(self): + """ + The commit_on_success context manager also works with a using argument. + """ + with self.assertRaises(Exception): + with transaction.commit_on_success(using="default"): + self.create_reporter_and_fail() + + self.assertEqual(Reporter.objects.count(), 0) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success_succeed(self): + """ + If there aren't any exceptions, the data will get saved. + """ + Reporter.objects.create(first_name="Alice", last_name="Smith") + with transaction.commit_on_success(): + Reporter.objects.filter(first_name="Alice").delete() + + self.assertQuerysetEqual(Reporter.objects.all(), []) + + @skipUnlessDBFeature('supports_transactions') + def test_commit_on_success_exit(self): + with transaction.autocommit(): + with transaction.commit_on_success(): + Reporter.objects.create(first_name="Bobby", last_name="Tables") + + # Much more formal + r = Reporter.objects.get() + r.first_name = "Robert" + r.save() + + r = Reporter.objects.get() + self.assertEqual(r.first_name, "Robert") + + @skipUnlessDBFeature('supports_transactions') + def test_manually_managed(self): + """ + You can manually manage transactions if you really want to, but you + have to remember to commit/rollback. + """ + with transaction.commit_manually(): + Reporter.objects.create(first_name="Libby", last_name="Holtzman") + transaction.commit() + self.assertEqual(Reporter.objects.count(), 1) + + @skipUnlessDBFeature('supports_transactions') + def test_manually_managed_mistake(self): + """ + If you forget, you'll get bad errors. + """ + with self.assertRaises(transaction.TransactionManagementError): + with transaction.commit_manually(): + Reporter.objects.create(first_name="Scott", last_name="Browning") + + @skipUnlessDBFeature('supports_transactions') + def test_manually_managed_with_using(self): + """ + The commit_manually function also works with a using argument. + """ + with self.assertRaises(transaction.TransactionManagementError): + with transaction.commit_manually(using="default"): + Reporter.objects.create(first_name="Walter", last_name="Cronkite") + + @skipUnlessDBFeature('requires_rollback_on_dirty_transaction') + def test_bad_sql(self): + """ + Regression for #11900: If a block wrapped by commit_on_success + writes a transaction that can't be committed, that transaction should + be rolled back. The bug is only visible using the psycopg2 backend, + though the fix is generally a good idea. + """ + with self.assertRaises(IntegrityError): + with transaction.commit_on_success(): + cursor = connection.cursor() + cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") + transaction.rollback() diff --git a/tests/regressiontests/text/__init__.py b/tests/transactions_regress/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/text/__init__.py +++ b/tests/transactions_regress/__init__.py diff --git a/tests/regressiontests/transactions_regress/models.py b/tests/transactions_regress/models.py index e8bca8ada2..e09e81d93d 100644 --- a/tests/regressiontests/transactions_regress/models.py +++ b/tests/transactions_regress/models.py @@ -1,10 +1,15 @@ from django.db import models + class Mod(models.Model): fld = models.IntegerField() +class SubMod(Mod): + cnt = models.IntegerField(unique=True) + class M2mA(models.Model): others = models.ManyToManyField('M2mB') + class M2mB(models.Model): fld = models.IntegerField() diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/transactions_regress/tests.py index 5d1ab2c6f6..fb3f257dab 100644 --- a/tests/regressiontests/transactions_regress/tests.py +++ b/tests/transactions_regress/tests.py @@ -1,15 +1,35 @@ from __future__ import absolute_import -from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS, DatabaseError +from django.db import (connection, connections, transaction, DEFAULT_DB_ALIAS, DatabaseError, + IntegrityError) from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError from django.test import TransactionTestCase, skipUnlessDBFeature from django.test.utils import override_settings from django.utils.unittest import skipIf, skipUnless -from .models import Mod, M2mA, M2mB +from transactions.tests import IgnorePendingDeprecationWarningsMixin +from .models import Mod, M2mA, M2mB, SubMod -class TestTransactionClosing(TransactionTestCase): +class ModelInheritanceTests(TransactionTestCase): + def test_save(self): + # First, create a SubMod, then try to save another with conflicting + # cnt field. The problem was that transactions were committed after + # every parent save when not in managed transaction. As the cnt + # conflict is in the second model, we can check if the first save + # was committed or not. + SubMod(fld=1, cnt=1).save() + # We should have committed the transaction for the above - assert this. + connection.rollback() + self.assertEqual(SubMod.objects.count(), 1) + try: + SubMod(fld=2, cnt=1).save() + except IntegrityError: + connection.rollback() + self.assertEqual(SubMod.objects.count(), 1) + self.assertEqual(Mod.objects.count(), 1) + +class TestTransactionClosing(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): """ Tests to make sure that transactions are properly closed when they should be, and aren't left pending after operations @@ -42,7 +62,7 @@ class TestTransactionClosing(TransactionTestCase): @commit_manually def non_comitter(): "Execute a managed transaction with read-only operations and fail to commit" - _ = Mod.objects.count() + Mod.objects.count() self.assertRaises(TransactionManagementError, non_comitter) @@ -56,7 +76,7 @@ class TestTransactionClosing(TransactionTestCase): """ Perform a database query, then commit the transaction """ - _ = Mod.objects.count() + Mod.objects.count() transaction.commit() try: @@ -74,7 +94,7 @@ class TestTransactionClosing(TransactionTestCase): """ Perform a database query, then rollback the transaction """ - _ = Mod.objects.count() + Mod.objects.count() transaction.rollback() try: @@ -90,9 +110,9 @@ class TestTransactionClosing(TransactionTestCase): @commit_manually def fake_committer(): "Query, commit, then query again, leaving with a pending transaction" - _ = Mod.objects.count() + Mod.objects.count() transaction.commit() - _ = Mod.objects.count() + Mod.objects.count() self.assertRaises(TransactionManagementError, fake_committer) @@ -138,7 +158,8 @@ class TestTransactionClosing(TransactionTestCase): @transaction.commit_on_success def create_system_user(): "Create a user in a transaction" - user = User.objects.create_user(username='system', password='iamr00t', email='root@SITENAME.com') + user = User.objects.create_user(username='system', password='iamr00t', + email='root@SITENAME.com') # Redundant, just makes sure the user id was read back from DB Mod.objects.create(fld=user.pk) @@ -161,61 +182,137 @@ class TestTransactionClosing(TransactionTestCase): """ self.test_failing_query_transaction_closed() +@skipIf(connection.vendor == 'sqlite' and + (connection.settings_dict['NAME'] == ':memory:' or + not connection.settings_dict['NAME']), + 'Test uses multiple connections, but in-memory sqlite does not support this') +class TestNewConnection(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): + """ + Check that new connections don't have special behaviour. + """ + def setUp(self): + self._old_backend = connections[DEFAULT_DB_ALIAS] + settings = self._old_backend.settings_dict.copy() + new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS) + connections[DEFAULT_DB_ALIAS] = new_backend + + def tearDown(self): + try: + connections[DEFAULT_DB_ALIAS].abort() + connections[DEFAULT_DB_ALIAS].close() + finally: + connections[DEFAULT_DB_ALIAS] = self._old_backend + + def test_commit(self): + """ + Users are allowed to commit and rollback connections. + """ + connection.set_autocommit(False) + try: + # The starting value is False, not None. + self.assertIs(connection._dirty, False) + list(Mod.objects.all()) + self.assertTrue(connection.is_dirty()) + connection.commit() + self.assertFalse(connection.is_dirty()) + list(Mod.objects.all()) + self.assertTrue(connection.is_dirty()) + connection.rollback() + self.assertFalse(connection.is_dirty()) + finally: + connection.set_autocommit(True) + + def test_enter_exit_management(self): + orig_dirty = connection._dirty + connection.enter_transaction_management() + connection.leave_transaction_management() + self.assertEqual(orig_dirty, connection._dirty) + @skipUnless(connection.vendor == 'postgresql', "This test only valid for PostgreSQL") -class TestPostgresAutocommit(TransactionTestCase): +class TestPostgresAutocommitAndIsolation(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): """ - Tests to make sure psycopg2's autocommit mode is restored after entering - and leaving transaction management. Refs #16047. + Tests to make sure psycopg2's autocommit mode and isolation level + is restored after entering and leaving transaction management. + Refs #16047, #18130. """ def setUp(self): from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT, - ISOLATION_LEVEL_READ_COMMITTED) + ISOLATION_LEVEL_SERIALIZABLE, + TRANSACTION_STATUS_IDLE) self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT - self._read_committed = ISOLATION_LEVEL_READ_COMMITTED + self._serializable = ISOLATION_LEVEL_SERIALIZABLE + self._idle = TRANSACTION_STATUS_IDLE # We want a clean backend with autocommit = True, so # first we need to do a bit of work to have that. self._old_backend = connections[DEFAULT_DB_ALIAS] settings = self._old_backend.settings_dict.copy() opts = settings['OPTIONS'].copy() - opts['autocommit'] = True + opts['isolation_level'] = ISOLATION_LEVEL_SERIALIZABLE settings['OPTIONS'] = opts new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS) connections[DEFAULT_DB_ALIAS] = new_backend def tearDown(self): - connections[DEFAULT_DB_ALIAS] = self._old_backend + try: + connections[DEFAULT_DB_ALIAS].abort() + finally: + connections[DEFAULT_DB_ALIAS].close() + connections[DEFAULT_DB_ALIAS] = self._old_backend def test_initial_autocommit_state(self): - self.assertTrue(connection.features.uses_autocommit) - self.assertEqual(connection.isolation_level, self._autocommit) + # Autocommit is activated when the connection is created. + connection.cursor().close() + self.assertTrue(connection.autocommit) def test_transaction_management(self): transaction.enter_transaction_management() - transaction.managed(True) - self.assertEqual(connection.isolation_level, self._read_committed) + self.assertFalse(connection.autocommit) + self.assertEqual(connection.isolation_level, self._serializable) transaction.leave_transaction_management() - self.assertEqual(connection.isolation_level, self._autocommit) + self.assertTrue(connection.autocommit) def test_transaction_stacking(self): transaction.enter_transaction_management() - transaction.managed(True) - self.assertEqual(connection.isolation_level, self._read_committed) + self.assertFalse(connection.autocommit) + self.assertEqual(connection.isolation_level, self._serializable) transaction.enter_transaction_management() - self.assertEqual(connection.isolation_level, self._read_committed) + self.assertFalse(connection.autocommit) + self.assertEqual(connection.isolation_level, self._serializable) + + transaction.leave_transaction_management() + self.assertFalse(connection.autocommit) + self.assertEqual(connection.isolation_level, self._serializable) transaction.leave_transaction_management() - self.assertEqual(connection.isolation_level, self._read_committed) + self.assertTrue(connection.autocommit) + def test_enter_autocommit(self): + transaction.enter_transaction_management() + self.assertFalse(connection.autocommit) + self.assertEqual(connection.isolation_level, self._serializable) + list(Mod.objects.all()) + self.assertTrue(transaction.is_dirty()) + # Enter autocommit mode again. + transaction.enter_transaction_management(False) + self.assertFalse(transaction.is_dirty()) + self.assertEqual( + connection.connection.get_transaction_status(), + self._idle) + list(Mod.objects.all()) + self.assertFalse(transaction.is_dirty()) + transaction.leave_transaction_management() + self.assertFalse(connection.autocommit) + self.assertEqual(connection.isolation_level, self._serializable) transaction.leave_transaction_management() - self.assertEqual(connection.isolation_level, self._autocommit) + self.assertTrue(connection.autocommit) -class TestManyToManyAddTransaction(TransactionTestCase): +class TestManyToManyAddTransaction(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): def test_manyrelated_add_commit(self): "Test for https://code.djangoproject.com/ticket/16818" a = M2mA.objects.create() @@ -230,8 +327,10 @@ class TestManyToManyAddTransaction(TransactionTestCase): self.assertEqual(a.others.count(), 1) -class SavepointTest(TransactionTestCase): +class SavepointTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase): + @skipIf(connection.vendor == 'sqlite', + "SQLite doesn't support savepoints in managed mode") @skipUnlessDBFeature('uses_savepoints') def test_savepoint_commit(self): @commit_manually @@ -239,7 +338,7 @@ class SavepointTest(TransactionTestCase): mod = Mod.objects.create(fld=1) pk = mod.pk sid = transaction.savepoint() - mod1 = Mod.objects.filter(pk=pk).update(fld=10) + Mod.objects.filter(pk=pk).update(fld=10) transaction.savepoint_commit(sid) mod2 = Mod.objects.get(pk=pk) transaction.commit() @@ -247,7 +346,9 @@ class SavepointTest(TransactionTestCase): work() - @skipIf(connection.vendor == 'mysql' and \ + @skipIf(connection.vendor == 'sqlite', + "SQLite doesn't support savepoints in managed mode") + @skipIf(connection.vendor == 'mysql' and connection.features._mysql_storage_engine == 'MyISAM', "MyISAM MySQL storage engine doesn't support savepoints") @skipUnlessDBFeature('uses_savepoints') @@ -257,7 +358,7 @@ class SavepointTest(TransactionTestCase): mod = Mod.objects.create(fld=1) pk = mod.pk sid = transaction.savepoint() - mod1 = Mod.objects.filter(pk=pk).update(fld=20) + Mod.objects.filter(pk=pk).update(fld=20) transaction.savepoint_rollback(sid) mod2 = Mod.objects.get(pk=pk) transaction.commit() diff --git a/tests/regressiontests/mail/__init__.py b/tests/unmanaged_models/__init__.py index 139597f9cb..139597f9cb 100644 --- a/tests/regressiontests/mail/__init__.py +++ b/tests/unmanaged_models/__init__.py diff --git a/tests/modeltests/unmanaged_models/models.py b/tests/unmanaged_models/models.py index 0eef69977c..0eef69977c 100644 --- a/tests/modeltests/unmanaged_models/models.py +++ b/tests/unmanaged_models/models.py diff --git a/tests/modeltests/unmanaged_models/tests.py b/tests/unmanaged_models/tests.py index 64e33bbb47..64e33bbb47 100644 --- a/tests/modeltests/unmanaged_models/tests.py +++ b/tests/unmanaged_models/tests.py diff --git a/tests/regressiontests/transactions_regress/__init__.py b/tests/update/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/transactions_regress/__init__.py +++ b/tests/update/__init__.py diff --git a/tests/modeltests/update/models.py b/tests/update/models.py index 08472d98b1..08472d98b1 100644 --- a/tests/modeltests/update/models.py +++ b/tests/update/models.py diff --git a/tests/modeltests/update/tests.py b/tests/update/tests.py index 7a1177c1fd..7a1177c1fd 100644 --- a/tests/modeltests/update/tests.py +++ b/tests/update/tests.py diff --git a/tests/regressiontests/urlpatterns_reverse/__init__.py b/tests/update_only_fields/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/urlpatterns_reverse/__init__.py +++ b/tests/update_only_fields/__init__.py diff --git a/tests/modeltests/update_only_fields/models.py b/tests/update_only_fields/models.py index bf5dd99166..bf5dd99166 100644 --- a/tests/modeltests/update_only_fields/models.py +++ b/tests/update_only_fields/models.py diff --git a/tests/modeltests/update_only_fields/tests.py b/tests/update_only_fields/tests.py index 9ca0f968d1..97c05ddc79 100644 --- a/tests/modeltests/update_only_fields/tests.py +++ b/tests/update_only_fields/tests.py @@ -207,6 +207,9 @@ class UpdateOnlyFieldsTests(TestCase): self.assertEqual(len(post_save_data[0]), 1) self.assertTrue('name' in post_save_data[0]) + pre_save.disconnect(pre_save_receiver) + post_save.disconnect(post_save_receiver) + def test_update_fields_incorrect_params(self): s = Person.objects.create(name='Sara', gender='F') @@ -233,6 +236,9 @@ class UpdateOnlyFieldsTests(TestCase): self.assertEqual(len(pre_save_data), 0) self.assertEqual(len(post_save_data), 0) + pre_save.disconnect(pre_save_receiver) + post_save.disconnect(post_save_receiver) + def test_num_queries_inheritance(self): s = Employee.objects.create(name='Sara', gender='F') s.employee_num = 1 diff --git a/tests/regressiontests/utils/__init__.py b/tests/urlpatterns_reverse/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/utils/__init__.py +++ b/tests/urlpatterns_reverse/__init__.py diff --git a/tests/urlpatterns_reverse/erroneous_urls.py b/tests/urlpatterns_reverse/erroneous_urls.py new file mode 100644 index 0000000000..ac6b6f7cb3 --- /dev/null +++ b/tests/urlpatterns_reverse/erroneous_urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import patterns, url + +urlpatterns = patterns('', + # View has erroneous import + url(r'erroneous_inner/$', 'urlpatterns_reverse.views.erroneous_view'), + # Module has erroneous import + url(r'erroneous_outer/$', 'urlpatterns_reverse.erroneous_views_module.erroneous_view'), + # View does not exist + url(r'missing_inner/$', 'urlpatterns_reverse.views.missing_view'), + # View is not callable + url(r'uncallable/$', 'urlpatterns_reverse.views.uncallable'), + # Module does not exist + url(r'missing_outer/$', 'urlpatterns_reverse.missing_module.missing_view'), + # Regex contains an error (refs #6170) + url(r'(regex_error/$', 'regressiontestes.urlpatterns_reverse.views.empty_view'), +) diff --git a/tests/regressiontests/urlpatterns_reverse/erroneous_views_module.py b/tests/urlpatterns_reverse/erroneous_views_module.py index 7f6b75e00f..7f6b75e00f 100644 --- a/tests/regressiontests/urlpatterns_reverse/erroneous_views_module.py +++ b/tests/urlpatterns_reverse/erroneous_views_module.py diff --git a/tests/regressiontests/urlpatterns_reverse/extra_urls.py b/tests/urlpatterns_reverse/extra_urls.py index c737f8b5c4..d3a04e6b31 100644 --- a/tests/regressiontests/urlpatterns_reverse/extra_urls.py +++ b/tests/urlpatterns_reverse/extra_urls.py @@ -12,6 +12,6 @@ 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')), + url('', include('urlpatterns_reverse.included_urls2')), + url(r'^prefix/(?P<prefix>\w+)/', include('urlpatterns_reverse.included_urls2')), ) diff --git a/tests/regressiontests/urlpatterns_reverse/included_named_urls.py b/tests/urlpatterns_reverse/included_named_urls.py index af03097df1..353aed255e 100644 --- a/tests/regressiontests/urlpatterns_reverse/included_named_urls.py +++ b/tests/urlpatterns_reverse/included_named_urls.py @@ -9,6 +9,6 @@ urlpatterns = patterns('', url(r'^$', empty_view, name="named-url3"), url(r'^extra/(?P<extra>\w+)/$', empty_view, name="named-url4"), url(r'^(?P<one>\d+)|(?P<two>\d+)/$', empty_view), - (r'^included/', include('regressiontests.urlpatterns_reverse.included_named_urls2')), + (r'^included/', include('urlpatterns_reverse.included_named_urls2')), ) diff --git a/tests/regressiontests/urlpatterns_reverse/included_named_urls2.py b/tests/urlpatterns_reverse/included_named_urls2.py index b31bdb1f7e..b31bdb1f7e 100644 --- a/tests/regressiontests/urlpatterns_reverse/included_named_urls2.py +++ b/tests/urlpatterns_reverse/included_named_urls2.py diff --git a/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py b/tests/urlpatterns_reverse/included_namespace_urls.py index 8fe69ab8ed..7a2096ecd9 100644 --- a/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py +++ b/tests/urlpatterns_reverse/included_namespace_urls.py @@ -8,7 +8,7 @@ from .views import view_class_instance testobj3 = URLObject('testapp', 'test-ns3') -urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', +urlpatterns = patterns('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'), @@ -20,7 +20,7 @@ urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', url(r'^view_class/(?P<arg1>\d+)/(?P<arg2>\d+)/$', view_class_instance, name='inc-view-class'), (r'^test3/', include(testobj3.urls)), - (r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')), - (r'^ns-included4/', include('regressiontests.urlpatterns_reverse.namespace_urls', namespace='inc-ns4')), + (r'^ns-included3/', include('urlpatterns_reverse.included_urls', namespace='inc-ns3')), + (r'^ns-included4/', include('urlpatterns_reverse.namespace_urls', namespace='inc-ns4')), ) diff --git a/tests/regressiontests/urlpatterns_reverse/included_urls.py b/tests/urlpatterns_reverse/included_urls.py index c8c9001843..c8c9001843 100644 --- a/tests/regressiontests/urlpatterns_reverse/included_urls.py +++ b/tests/urlpatterns_reverse/included_urls.py diff --git a/tests/regressiontests/urlpatterns_reverse/included_urls2.py b/tests/urlpatterns_reverse/included_urls2.py index 98605047f4..98605047f4 100644 --- a/tests/regressiontests/urlpatterns_reverse/included_urls2.py +++ b/tests/urlpatterns_reverse/included_urls2.py diff --git a/tests/urlpatterns_reverse/middleware.py b/tests/urlpatterns_reverse/middleware.py new file mode 100644 index 0000000000..fbf577786e --- /dev/null +++ b/tests/urlpatterns_reverse/middleware.py @@ -0,0 +1,35 @@ +from __future__ import absolute_import + +from django.core.urlresolvers import reverse +from django.http import HttpResponse, StreamingHttpResponse + +from . 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 + +class ReverseInnerInResponseMiddleware(object): + def process_response(self, *args, **kwargs): + return HttpResponse(reverse('inner')) + +class ReverseOuterInResponseMiddleware(object): + def process_response(self, *args, **kwargs): + return HttpResponse(reverse('outer')) + +class ReverseInnerInStreaming(object): + def process_view(self, *args, **kwargs): + def stream(): + yield reverse('inner') + return StreamingHttpResponse(stream()) + +class ReverseOuterInStreaming(object): + def process_view(self, *args, **kwargs): + def stream(): + yield reverse('outer') + return StreamingHttpResponse(stream()) diff --git a/tests/regressiontests/utils/test_module/__init__.py b/tests/urlpatterns_reverse/models.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/utils/test_module/__init__.py +++ b/tests/urlpatterns_reverse/models.py diff --git a/tests/regressiontests/urlpatterns_reverse/named_urls.py b/tests/urlpatterns_reverse/named_urls.py index 8df336cf4b..3290cab29c 100644 --- a/tests/regressiontests/urlpatterns_reverse/named_urls.py +++ b/tests/urlpatterns_reverse/named_urls.py @@ -9,5 +9,5 @@ urlpatterns = patterns('', url(r'^$', empty_view, name="named-url1"), url(r'^extra/(?P<extra>\w+)/$', empty_view, name="named-url2"), url(r'^(?P<one>\d+)|(?P<two>\d+)/$', empty_view), - (r'^included/', include('regressiontests.urlpatterns_reverse.included_named_urls')), + (r'^included/', include('urlpatterns_reverse.included_named_urls')), ) diff --git a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/tests/urlpatterns_reverse/namespace_urls.py index ab2e77af24..cf960070ec 100644 --- a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py +++ b/tests/urlpatterns_reverse/namespace_urls.py @@ -25,7 +25,7 @@ default_testobj = URLObject('testapp', 'testapp') otherobj1 = URLObject('nodefault', 'other-ns1') otherobj2 = URLObject('nodefault', 'other-ns2') -urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', +urlpatterns = patterns('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'), url(r'^resolver_match/$', 'pass_resolver_match_view', name='test-resolver-match'), @@ -47,13 +47,13 @@ urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', (r'^other1/', include(otherobj1.urls)), (r'^other[246]/', include(otherobj2.urls)), - (r'^ns-included[135]/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), - (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), + (r'^ns-included[135]/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), + (r'^ns-included2/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), - (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')), - (r'^inc(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns5')), + (r'^included/', include('urlpatterns_reverse.included_namespace_urls')), + (r'^inc(?P<outer>\d+)/', include('urlpatterns_reverse.included_urls', namespace='inc-ns5')), - (r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')), + (r'^ns-outer/(?P<outer>\d+)/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')), - (r'^\+\\\$\*/', include('regressiontests.urlpatterns_reverse.namespace_urls', namespace='special')), + (r'^\+\\\$\*/', include('urlpatterns_reverse.namespace_urls', namespace='special')), ) diff --git a/tests/regressiontests/urlpatterns_reverse/no_urls.py b/tests/urlpatterns_reverse/no_urls.py index 5b1959c7dc..5b1959c7dc 100644 --- a/tests/regressiontests/urlpatterns_reverse/no_urls.py +++ b/tests/urlpatterns_reverse/no_urls.py diff --git a/tests/regressiontests/urlpatterns_reverse/reverse_lazy_urls.py b/tests/urlpatterns_reverse/reverse_lazy_urls.py index 693c6e1b38..693c6e1b38 100644 --- a/tests/regressiontests/urlpatterns_reverse/reverse_lazy_urls.py +++ b/tests/urlpatterns_reverse/reverse_lazy_urls.py diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index eb3afe8201..24f96fac7c 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -9,7 +9,7 @@ from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.urlresolvers import (reverse, resolve, get_callable, get_resolver, NoReverseMatch, Resolver404, ResolverMatch, RegexURLResolver, RegexURLPattern) -from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect +from django.http import HttpRequest, HttpResponseRedirect, HttpResponsePermanentRedirect from django.shortcuts import redirect from django.test import TestCase from django.utils import unittest, six @@ -30,8 +30,8 @@ resolve_test_data = ( ('/included/mixed_args/42/37/', 'inc-mixed-args', None, '', views.empty_view, tuple(), {'arg2': '37'}), # Unnamed views will be resolved to the function/class name - ('/unnamed/normal/42/37/', 'regressiontests.urlpatterns_reverse.views.empty_view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), - ('/unnamed/view_class/42/37/', 'regressiontests.urlpatterns_reverse.views.ViewClass', None, '', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/unnamed/normal/42/37/', 'urlpatterns_reverse.views.empty_view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/unnamed/view_class/42/37/', 'urlpatterns_reverse.views.ViewClass', None, '', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), # If you have no kwargs, you get an args list. ('/no_kwargs/42/37/', 'no-kwargs', None, '', views.empty_view, ('42','37'), {}), @@ -97,7 +97,11 @@ test_data = ( ('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', r'/special_chars/%2B%5C%24%2A/', [r'+\$*'], {}), + ('special', r'/special_chars/some%20resource/', [r'some resource'], {}), + ('special', r'/special_chars/10%25%20complete/', [r'10% complete'], {}), + ('special', r'/special_chars/some%20resource/', [], {'chars': r'some resource'}), + ('special', r'/special_chars/10%25%20complete/', [], {'chars': r'10% complete'}), ('special', NoReverseMatch, [''], {}), ('mixed', '/john/0/', [], {'name': 'john'}), ('repeats', '/repeats/a/', [], {}), @@ -131,8 +135,8 @@ test_data = ( # 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}), + ('urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}), + ('urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}), ('non_path_include', '/includes/non_path_include/', [], {}), # Tests for #13154 @@ -143,7 +147,7 @@ test_data = ( ) class NoURLPatternsTests(TestCase): - urls = 'regressiontests.urlpatterns_reverse.no_urls' + urls = 'urlpatterns_reverse.no_urls' def test_no_urls_exception(self): """ @@ -152,11 +156,11 @@ class NoURLPatternsTests(TestCase): resolver = RegexURLResolver(r'^$', self.urls) self.assertRaisesMessage(ImproperlyConfigured, - "The included urlconf regressiontests.urlpatterns_reverse.no_urls "\ + "The included urlconf urlpatterns_reverse.no_urls "\ "doesn't have any patterns in it", getattr, resolver, 'url_patterns') class URLPatternReverse(TestCase): - urls = 'regressiontests.urlpatterns_reverse.urls' + urls = 'urlpatterns_reverse.urls' def test_urlpattern_reverse(self): for name, expected, args, kwargs in test_data: @@ -183,6 +187,11 @@ class URLPatternReverse(TestCase): self.assertEqual('/bump%2520map/includes/non_path_include/', reverse('non_path_include', prefix='/bump%20map/')) + def test_non_urlsafe_prefix_with_args(self): + # Regression for #20022 + self.assertEqual('/%7Eme/places/1/', + reverse('places', args=[1], prefix='/~me/')) + class ResolverTests(unittest.TestCase): def test_resolver_repr(self): """ @@ -190,7 +199,7 @@ class ResolverTests(unittest.TestCase): (#17892). """ # Pick a resolver from a namespaced urlconf - resolver = get_resolver('regressiontests.urlpatterns_reverse.namespace_urls') + resolver = get_resolver('urlpatterns_reverse.namespace_urls') sub_resolver = resolver.namespace_dict['test-ns1'][1] self.assertIn('<RegexURLPattern list>', repr(sub_resolver)) @@ -214,7 +223,7 @@ class ResolverTests(unittest.TestCase): exception contains a list in the right format for printing out in the DEBUG 404 page with both the patterns and URL names, if available. """ - urls = 'regressiontests.urlpatterns_reverse.named_urls' + urls = 'urlpatterns_reverse.named_urls' # this list matches the expected URL types and names returned when # you try to resolve a non-existent URL in the first level of included # URLs in named_urls.py (e.g., '/included/non-existent-url') @@ -245,7 +254,7 @@ class ResolverTests(unittest.TestCase): self.assertEqual(t.name, e['name'], 'Wrong URL name. Expected "%s", got "%s".' % (e['name'], t.name)) class ReverseLazyTest(TestCase): - urls = 'regressiontests.urlpatterns_reverse.reverse_lazy_urls' + urls = 'urlpatterns_reverse.reverse_lazy_urls' def test_redirect_with_lazy_reverse(self): response = self.client.get('/redirect/') @@ -260,7 +269,7 @@ class ReverseLazyTest(TestCase): self.assertEqual(response.status_code, 200) class ReverseShortcutTests(TestCase): - urls = 'regressiontests.urlpatterns_reverse.urls' + urls = 'urlpatterns_reverse.urls' def test_redirect_to_object(self): # We don't really need a model; just something with a get_absolute_url @@ -270,36 +279,36 @@ class ReverseShortcutTests(TestCase): res = redirect(FakeObj()) self.assertTrue(isinstance(res, HttpResponseRedirect)) - self.assertEqual(res['Location'], '/hi-there/') + self.assertEqual(res.url, '/hi-there/') res = redirect(FakeObj(), permanent=True) self.assertTrue(isinstance(res, HttpResponsePermanentRedirect)) - self.assertEqual(res['Location'], '/hi-there/') + self.assertEqual(res.url, '/hi-there/') def test_redirect_to_view_name(self): res = redirect('hardcoded2') - self.assertEqual(res['Location'], '/hardcoded/doc.pdf') + self.assertEqual(res.url, '/hardcoded/doc.pdf') res = redirect('places', 1) - self.assertEqual(res['Location'], '/places/1/') + self.assertEqual(res.url, '/places/1/') res = redirect('headlines', year='2008', month='02', day='17') - self.assertEqual(res['Location'], '/headlines/2008.02.17/') + self.assertEqual(res.url, '/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/') + self.assertEqual(res.url, '/foo/') res = redirect('http://example.com/') - self.assertEqual(res['Location'], 'http://example.com/') + self.assertEqual(res.url, '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.assertEqual(res.url, '/absolute_arg_view/') self.assertRaises(NoReverseMatch, redirect, absolute_kwargs_view, wrong_argument=None) class NamespaceTests(TestCase): - urls = 'regressiontests.urlpatterns_reverse.namespace_urls' + urls = 'urlpatterns_reverse.namespace_urls' def test_ambiguous_object(self): "Names deployed via dynamic URL objects that require namespaces can't be resolved" @@ -453,13 +462,66 @@ class RequestURLconfTests(TestCase): ) self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/') + def test_reverse_inner_in_response_middleware(self): + """ + Test reversing an URL from the *overridden* URLconf from inside + a response middleware. + """ + settings.MIDDLEWARE_CLASSES += ( + '%s.ChangeURLconfMiddleware' % middleware.__name__, + '%s.ReverseInnerInResponseMiddleware' % middleware.__name__, + ) + response = self.client.get('/second_test/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b'/second_test/') + + def test_reverse_outer_in_response_middleware(self): + """ + Test reversing an URL from the *default* URLconf from inside + a response middleware. + """ + settings.MIDDLEWARE_CLASSES += ( + '%s.ChangeURLconfMiddleware' % middleware.__name__, + '%s.ReverseOuterInResponseMiddleware' % middleware.__name__, + ) + message = "Reverse for 'outer' with arguments '()' and keyword arguments '{}' not found." + with self.assertRaisesMessage(NoReverseMatch, message): + self.client.get('/second_test/') + + def test_reverse_inner_in_streaming(self): + """ + Test reversing an URL from the *overridden* URLconf from inside + a streaming response. + """ + settings.MIDDLEWARE_CLASSES += ( + '%s.ChangeURLconfMiddleware' % middleware.__name__, + '%s.ReverseInnerInStreaming' % middleware.__name__, + ) + response = self.client.get('/second_test/') + self.assertEqual(response.status_code, 200) + self.assertEqual(b''.join(response), b'/second_test/') + + def test_reverse_outer_in_streaming(self): + """ + Test reversing an URL from the *default* URLconf from inside + a streaming response. + """ + settings.MIDDLEWARE_CLASSES += ( + '%s.ChangeURLconfMiddleware' % middleware.__name__, + '%s.ReverseOuterInStreaming' % middleware.__name__, + ) + message = "Reverse for 'outer' with arguments '()' and keyword arguments '{}' not found." + with self.assertRaisesMessage(NoReverseMatch, message): + self.client.get('/second_test/') + b''.join(self.client.get('/second_test/')) + class ErrorHandlerResolutionTests(TestCase): """Tests for handler404 and handler500""" def setUp(self): from django.core.urlresolvers import RegexURLResolver - urlconf = 'regressiontests.urlpatterns_reverse.urls_error_handlers' - urlconf_callables = 'regressiontests.urlpatterns_reverse.urls_error_handlers_callables' + urlconf = 'urlpatterns_reverse.urls_error_handlers' + urlconf_callables = 'urlpatterns_reverse.urls_error_handlers_callables' self.resolver = RegexURLResolver(r'^$', urlconf) self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables) @@ -476,7 +538,7 @@ class ErrorHandlerResolutionTests(TestCase): self.assertEqual(self.callable_resolver.resolve500(), handler) class DefaultErrorHandlerTests(TestCase): - urls = 'regressiontests.urlpatterns_reverse.urls_without_full_import' + urls = 'urlpatterns_reverse.urls_without_full_import' def test_default_handler(self): "If the urls.py doesn't specify handlers, the defaults are used" @@ -499,7 +561,7 @@ class NoRootUrlConfTests(TestCase): self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/') class ResolverMatchTests(TestCase): - urls = 'regressiontests.urlpatterns_reverse.namespace_urls' + urls = 'urlpatterns_reverse.namespace_urls' def test_urlpattern_resolve(self): for path, name, app_name, namespace, func, args, kwargs in resolve_test_data: @@ -529,8 +591,12 @@ class ResolverMatchTests(TestCase): resolver_match = response.resolver_match self.assertEqual(resolver_match.url_name, 'test-resolver-match') + def test_resolver_match_on_request_before_resolution(self): + request = HttpRequest() + self.assertIsNone(request.resolver_match) + class ErroneousViewTests(TestCase): - urls = 'regressiontests.urlpatterns_reverse.erroneous_urls' + urls = 'urlpatterns_reverse.erroneous_urls' def test_erroneous_resolve(self): self.assertRaises(ImportError, self.client.get, '/erroneous_inner/') @@ -554,9 +620,9 @@ class ViewLoadingTests(TestCase): # ViewDoesNotExist, ... six.assertRaisesRegex(self, ViewDoesNotExist, ".*View does not exist in.*", get_callable, - 'regressiontests.urlpatterns_reverse.views.i_should_not_exist') + 'urlpatterns_reverse.views.i_should_not_exist') # ... but if the AttributeError is caused by something else don't # swallow it. self.assertRaises(AttributeError, get_callable, - 'regressiontests.urlpatterns_reverse.views_broken.i_am_broken') + 'urlpatterns_reverse.views_broken.i_am_broken') diff --git a/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py b/tests/urlpatterns_reverse/urlconf_inner.py index 6d9d2346f7..6d9d2346f7 100644 --- a/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py +++ b/tests/urlpatterns_reverse/urlconf_inner.py diff --git a/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py b/tests/urlpatterns_reverse/urlconf_outer.py index 0cdebf83ff..0cdebf83ff 100644 --- a/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py +++ b/tests/urlpatterns_reverse/urlconf_outer.py diff --git a/tests/regressiontests/urlpatterns_reverse/urls.py b/tests/urlpatterns_reverse/urls.py index 1d4ae73c67..1dbc8d889f 100644 --- a/tests/regressiontests/urlpatterns_reverse/urls.py +++ b/tests/urlpatterns_reverse/urls.py @@ -40,7 +40,7 @@ urlpatterns = patterns('', 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'^special_chars/(?P<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"), @@ -49,8 +49,8 @@ urlpatterns = patterns('', 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')), + include('urlpatterns_reverse.included_urls')), + url('', include('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"), diff --git a/tests/urlpatterns_reverse/urls_error_handlers.py b/tests/urlpatterns_reverse/urls_error_handlers.py new file mode 100644 index 0000000000..be4f42afbc --- /dev/null +++ b/tests/urlpatterns_reverse/urls_error_handlers.py @@ -0,0 +1,8 @@ +# Used by the ErrorHandlerResolutionTests test case. + +from django.conf.urls import patterns + +urlpatterns = patterns('') + +handler404 = 'urlpatterns_reverse.views.empty_view' +handler500 = 'urlpatterns_reverse.views.empty_view' diff --git a/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py b/tests/urlpatterns_reverse/urls_error_handlers_callables.py index fe2d3137e9..fe2d3137e9 100644 --- a/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py +++ b/tests/urlpatterns_reverse/urls_error_handlers_callables.py diff --git a/tests/regressiontests/urlpatterns_reverse/urls_without_full_import.py b/tests/urlpatterns_reverse/urls_without_full_import.py index ca3e424f23..ca3e424f23 100644 --- a/tests/regressiontests/urlpatterns_reverse/urls_without_full_import.py +++ b/tests/urlpatterns_reverse/urls_without_full_import.py diff --git a/tests/regressiontests/urlpatterns_reverse/views.py b/tests/urlpatterns_reverse/views.py index 88d169a118..88d169a118 100644 --- a/tests/regressiontests/urlpatterns_reverse/views.py +++ b/tests/urlpatterns_reverse/views.py diff --git a/tests/regressiontests/urlpatterns_reverse/views_broken.py b/tests/urlpatterns_reverse/views_broken.py index 4953aab239..4953aab239 100644 --- a/tests/regressiontests/urlpatterns_reverse/views_broken.py +++ b/tests/urlpatterns_reverse/views_broken.py diff --git a/tests/urls.py b/tests/urls.py index 4f37ab5f52..1540e71e18 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,33 +1,33 @@ from django.conf.urls import patterns, include urlpatterns = patterns('', - # test_client modeltest urls - (r'^test_client/', include('modeltests.test_client.urls')), - (r'^test_client_regress/', include('regressiontests.test_client_regress.urls')), + # test_client urls + (r'^test_client/', include('test_client.urls')), + (r'^test_client_regress/', include('test_client_regress.urls')), # File upload test views - (r'^file_uploads/', include('regressiontests.file_uploads.urls')), + (r'^file_uploads/', include('file_uploads.urls')), # Always provide the auth system login and logout views (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), (r'^accounts/logout/$', 'django.contrib.auth.views.logout'), # test urlconf for {% url %} template tag - (r'^url_tag/', include('regressiontests.templates.urls')), + (r'^url_tag/', include('template_tests.urls')), # django built-in views - (r'^views/', include('regressiontests.views.urls')), + (r'^views/', include('view_tests.urls')), # test urlconf for middleware tests - (r'^middleware/', include('regressiontests.middleware.urls')), + (r'^middleware/', include('middleware.urls')), # admin widget tests - (r'widget_admin/', include('regressiontests.admin_widgets.urls')), + (r'widget_admin/', include('admin_widgets.urls')), # admin custom URL tests - (r'^custom_urls/', include('regressiontests.admin_custom_urls.urls')), + (r'^custom_urls/', include('admin_custom_urls.urls')), # admin scripts tests - (r'^admin_scripts/', include('regressiontests.admin_scripts.urls')), + (r'^admin_scripts/', include('admin_scripts.urls')), ) diff --git a/tests/regressiontests/version/__init__.py b/tests/user_commands/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/version/__init__.py +++ b/tests/user_commands/__init__.py diff --git a/tests/regressiontests/views/templatetags/__init__.py b/tests/user_commands/management/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/views/templatetags/__init__.py +++ b/tests/user_commands/management/__init__.py diff --git a/tests/regressiontests/wsgi/__init__.py b/tests/user_commands/management/commands/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/wsgi/__init__.py +++ b/tests/user_commands/management/commands/__init__.py diff --git a/tests/modeltests/user_commands/management/commands/dance.py b/tests/user_commands/management/commands/dance.py index 911530d223..911530d223 100644 --- a/tests/modeltests/user_commands/management/commands/dance.py +++ b/tests/user_commands/management/commands/dance.py diff --git a/tests/user_commands/management/commands/leave_locale_alone_false.py b/tests/user_commands/management/commands/leave_locale_alone_false.py new file mode 100644 index 0000000000..8ebb607d5a --- /dev/null +++ b/tests/user_commands/management/commands/leave_locale_alone_false.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand +from django.utils import translation + +class Command(BaseCommand): + + can_import_settings = True + leave_locale_alone = False + + def handle(self, *args, **options): + return translation.get_language() diff --git a/tests/user_commands/management/commands/leave_locale_alone_true.py b/tests/user_commands/management/commands/leave_locale_alone_true.py new file mode 100644 index 0000000000..e0f923591e --- /dev/null +++ b/tests/user_commands/management/commands/leave_locale_alone_true.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand +from django.utils import translation + +class Command(BaseCommand): + + can_import_settings = True + leave_locale_alone = True + + def handle(self, *args, **options): + return translation.get_language() diff --git a/tests/modeltests/user_commands/models.py b/tests/user_commands/models.py index f2aa549bb9..f2aa549bb9 100644 --- a/tests/modeltests/user_commands/models.py +++ b/tests/user_commands/models.py diff --git a/tests/modeltests/user_commands/tests.py b/tests/user_commands/tests.py index d25911c09f..bf555e66b9 100644 --- a/tests/modeltests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -1,13 +1,14 @@ import sys from django.core import management -from django.core.management.base import CommandError -from django.test import TestCase +from django.core.management import CommandError +from django.core.management.utils import popen_wrapper +from django.test import SimpleTestCase from django.utils import translation from django.utils.six import StringIO -class CommandTests(TestCase): +class CommandTests(SimpleTestCase): def test_command(self): out = StringIO() management.call_command('dance', stdout=out) @@ -44,3 +45,23 @@ class CommandTests(TestCase): finally: sys.stderr = old_stderr self.assertIn("CommandError", err.getvalue()) + + def test_default_en_us_locale_set(self): + # Forces en_us when set to true + out = StringIO() + with translation.override('pl'): + management.call_command('leave_locale_alone_false', stdout=out) + self.assertEqual(out.getvalue(), "en-us\n") + + def test_configured_locale_preserved(self): + # Leaves locale from settings when set to false + out = StringIO() + with translation.override('pl'): + management.call_command('leave_locale_alone_true', stdout=out) + self.assertEqual(out.getvalue(), "pl\n") + + +class UtilsTests(SimpleTestCase): + + def test_no_existent_external_program(self): + self.assertRaises(CommandError, popen_wrapper, ['a_42_command_that_doesnt_exist_42']) diff --git a/tests/utils_tests/__init__.py b/tests/utils_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/utils_tests/__init__.py diff --git a/tests/regressiontests/utils/archives/foobar.tar b/tests/utils_tests/archives/foobar.tar Binary files differindex 5418405070..5418405070 100644 --- a/tests/regressiontests/utils/archives/foobar.tar +++ b/tests/utils_tests/archives/foobar.tar diff --git a/tests/regressiontests/utils/archives/foobar.tar.bz2 b/tests/utils_tests/archives/foobar.tar.bz2 Binary files differindex 693529dcef..693529dcef 100644 --- a/tests/regressiontests/utils/archives/foobar.tar.bz2 +++ b/tests/utils_tests/archives/foobar.tar.bz2 diff --git a/tests/regressiontests/utils/archives/foobar.tar.gz b/tests/utils_tests/archives/foobar.tar.gz Binary files differindex 5b596bd58e..5b596bd58e 100644 --- a/tests/regressiontests/utils/archives/foobar.tar.gz +++ b/tests/utils_tests/archives/foobar.tar.gz diff --git a/tests/regressiontests/utils/archives/foobar.zip b/tests/utils_tests/archives/foobar.zip Binary files differindex dc3e2fc067..dc3e2fc067 100644 --- a/tests/regressiontests/utils/archives/foobar.zip +++ b/tests/utils_tests/archives/foobar.zip diff --git a/tests/regressiontests/utils/eggs/test_egg.egg b/tests/utils_tests/eggs/test_egg.egg Binary files differindex 9b08cc10ef..9b08cc10ef 100644 --- a/tests/regressiontests/utils/eggs/test_egg.egg +++ b/tests/utils_tests/eggs/test_egg.egg diff --git a/tests/utils_tests/files/strip_tags1.html b/tests/utils_tests/files/strip_tags1.html new file mode 100644 index 0000000000..8e9ee1d8fd --- /dev/null +++ b/tests/utils_tests/files/strip_tags1.html @@ -0,0 +1,1302 @@ +<!DOCTYPE html> +<html> + <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# githubog: http://ogp.me/ns/fb/githubog#"> + <meta charset='utf-8'> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <title>Improved regex in strip_tags · d7504a3 · django/django</title> + <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub" /> + <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub" /> + <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-114.png" /> + <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114.png" /> + <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-144.png" /> + <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144.png" /> + <link rel="logo" type="image/svg" href="http://github-media-downloads.s3.amazonaws.com/github-logo.svg" /> + <meta name="msapplication-TileImage" content="/windows-tile.png"> + <meta name="msapplication-TileColor" content="#ffffff"> + + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> + + <meta content="authenticity_token" name="csrf-param" /> +<meta content="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" name="csrf-token" /> + + <link href="https://a248.e.akamai.net/assets.github.com/assets/github-f70e4783e00fd4884a9e5e651a43933c9881caa8.css" media="all" rel="stylesheet" type="text/css" /> + <link href="https://a248.e.akamai.net/assets.github.com/assets/github2-0d31290d073dea4d8671e2b8c747629aeb074034.css" media="all" rel="stylesheet" type="text/css" /> + + + + <script src="https://a248.e.akamai.net/assets.github.com/assets/frameworks-d76b58e749b52bc47a4c46620bf2c320fabe5248.js" type="text/javascript"></script> + <script src="https://a248.e.akamai.net/assets.github.com/assets/github-67b55380cff8d6766b298e6935a3c1db7d5c6d5d.js" type="text/javascript"></script> + + <meta http-equiv="x-pjax-version" content="1212ad79754350a805cefbcd08a3dadf"> + + <link data-pjax-transient rel='alternate' type='text/plain+diff' href="/django/django/commit/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e.diff"> + <link data-pjax-transient rel='alternate' type='text/plain+patch' href="/django/django/commit/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e.patch"> + <link data-pjax-transient rel='permalink' type='text/html' href="/django/django/commit/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e"> + <meta property="og:title" content="django"/> + <meta property="og:type" content="githubog:gitrepository"/> + <meta property="og:url" content="https://github.com/django/django"/> + <meta property="og:image" content="https://secure.gravatar.com/avatar/fd542381031aa84dca86628ece84fc07?s=420&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png"/> + <meta property="og:site_name" content="GitHub"/> + <meta property="og:description" content="django - The Web framework for perfectionists with deadlines."/> + <meta property="twitter:card" content="summary"/> + <meta property="twitter:site" content="@GitHub"> + <meta property="twitter:title" content="django/django"/> + + <meta name="description" content="django - The Web framework for perfectionists with deadlines." /> + + <link href="https://github.com/django/django/commits/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e.atom" rel="alternate" title="Recent Commits to django:d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" type="application/atom+xml" /> + + </head> + + + <body class="logged_in linux vis-public env-production "> + <div id="wrapper"> + + <div class="header header-logged-in true"> + <div class="container clearfix"> + + <a class="header-logo-blacktocat" href="https://github.com/"> + <span class="mega-icon mega-icon-blacktocat"></span> +</a> + + <div class="divider-vertical"></div> + + <a href="/django/django/notifications" class="notification-indicator tooltipped downwards contextually-unread" title="You have unread notifications in this repository"> + <span class="mail-status unread"></span> + </a> + <div class="divider-vertical"></div> + + + <div class="command-bar js-command-bar "> + <form accept-charset="UTF-8" action="/search" class="command-bar-form" id="top_search_form" method="get"> + <a href="/search/advanced" class="advanced-search-icon tooltipped downwards command-bar-search" id="advanced_search" title="Advanced search"><span class="mini-icon mini-icon-advanced-search "></span></a> + + <input type="text" name="q" id="js-command-bar-field" placeholder="Search or type a command" tabindex="1" data-username="claudep" autocapitalize="off"> + + <span class="mini-icon help tooltipped downwards" title="Show command bar help"> + <span class="mini-icon mini-icon-help"></span> + </span> + + <input type="hidden" name="ref" value="commandbar"> + + <div class="divider-vertical"></div> +</form> + <ul class="top-nav"> + <li class="explore"><a href="https://github.com/explore">Explore</a></li> + <li><a href="https://gist.github.com">Gist</a></li> + <li><a href="/blog">Blog</a></li> + <li><a href="http://help.github.com">Help</a></li> + </ul> + </div> + + + + + <ul id="user-links"> + <li> + <a href="https://github.com/claudep" class="name"> + <img height="20" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="20" /> claudep + </a> + </li> + <li> + <a href="/new" id="new_repo" class="tooltipped downwards" title="Create a new repo"> + <span class="mini-icon mini-icon-create"></span> + </a> + </li> + <li> + <a href="/settings/profile" id="account_settings" + class="tooltipped downwards" + title="Account settings "> + <span class="mini-icon mini-icon-account-settings"></span> + </a> + </li> + <li> + <a href="/logout" data-method="post" id="logout" class="tooltipped downwards" title="Sign out"> + <span class="mini-icon mini-icon-logout"></span> + </a> + </li> + </ul> + + + + + </div> +</div> + + <div class="site hfeed" itemscope itemtype="http://schema.org/WebPage"> + <div class="hentry"> + + <div class="pagehead repohead instapaper_ignore readability-menu "> + <div class="container"> + <div class="title-actions-bar"> + + + +<ul class="pagehead-actions"> + + <li class="nspr"> + <a href="/django/django/pull/new/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" class="button minibutton btn-pull-request" icon_class="mini-icon-pull-request"><span class="mini-icon mini-icon-pull-request"></span>Pull Request</a> + </li> + + <li class="subscription"> + <form accept-charset="UTF-8" action="/notifications/subscribe" data-autosubmit="true" data-remote="true" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> <input id="repository_id" name="repository_id" type="hidden" value="4164482" /> + + <div class="select-menu js-menu-container js-select-menu"> + <span class="minibutton select-menu-button js-menu-target"> + <span class="js-select-button"> + <span class="mini-icon mini-icon-watching"></span> + Watch + </span> + </span> + + <div class="select-menu-modal-holder js-menu-content"> + <div class="select-menu-modal"> + <div class="select-menu-header"> + <span class="select-menu-title">Notification status</span> + <span class="mini-icon mini-icon-remove-close js-menu-close"></span> + </div> <!-- /.select-menu-header --> + + <div class="select-menu-list js-navigation-container"> + + <div class="select-menu-item js-navigation-item js-navigation-target selected"> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="do_included" name="do" type="radio" value="included" /> + <h4>Not watching</h4> + <span class="description">You only receive notifications for discussions in which you participate or are @mentioned.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-watching"></span> + Watch + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input id="do_subscribed" name="do" type="radio" value="subscribed" /> + <h4>Watching</h4> + <span class="description">You receive notifications for all discussions in this repository.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-unwatch"></span> + Unwatch + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input id="do_ignore" name="do" type="radio" value="ignore" /> + <h4>Ignoring</h4> + <span class="description">You do not receive any notifications for discussions in this repository.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-mute"></span> + Stop ignoring + </span> + </div> + </div> <!-- /.select-menu-item --> + + </div> <!-- /.select-menu-list --> + + </div> <!-- /.select-menu-modal --> + </div> <!-- /.select-menu-modal-holder --> + </div> <!-- /.select-menu --> + +</form> + </li> + + <li class="js-toggler-container js-social-container starring-container on"> + <a href="/django/django/unstar" class="minibutton js-toggler-target star-button starred upwards" title="Unstar this repo" data-remote="true" data-method="post" rel="nofollow"> + <span class="mini-icon mini-icon-remove-star"></span> + <span class="text">Unstar</span> + </a> + <a href="/django/django/star" class="minibutton js-toggler-target star-button unstarred upwards" title="Star this repo" data-remote="true" data-method="post" rel="nofollow"> + <span class="mini-icon mini-icon-star"></span> + <span class="text">Star</span> + </a> + <a class="social-count js-social-count" href="/django/django/stargazers">5,939</a> + </li> + + <li> + <a href="/django/django/fork_select" class="minibutton js-toggler-target fork-button lighter upwards" title="Fork this repo" rel="facebox nofollow"> + <span class="mini-icon mini-icon-branch-create"></span> + <span class="text">Fork</span> + </a> + <a href="/django/django/network" class="social-count">1,909</a> + </li> + + +</ul> + + <h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public"> + <span class="repo-label"><span>public</span></span> + <span class="mega-icon mega-icon-public-repo"></span> + <span class="author vcard"> + <a href="/django" class="url fn" itemprop="url" rel="author"> + <span itemprop="title">django</span> + </a></span> / + <strong><a href="/django/django" class="js-current-repository">django</a></strong> + </h1> + </div> + + + <ul class="tabs"> + <li><a href="/django/django" class="selected" highlight="repo_source repo_downloads repo_commits repo_tags repo_branches">Code</a></li> + <li><a href="/django/django/network" highlight="repo_network">Network</a></li> + <li><a href="/django/django/pulls" highlight="repo_pulls">Pull Requests <span class='counter'>177</span></a></li> + + + + + <li><a href="/django/django/graphs" highlight="repo_graphs repo_contributors">Graphs</a></li> + + <li> + <a href="/django/django/settings">Settings</a> + </li> + + </ul> + +<div class="tabnav"> + + <span class="tabnav-right"> + <ul class="tabnav-tabs"> + <li><a href="/django/django/tags" class="tabnav-tab" highlight="repo_tags">Tags <span class="counter ">39</span></a></li> + </ul> + + </span> + + <div class="tabnav-widget scope"> + + + <div class="select-menu js-menu-container js-select-menu js-branch-menu"> + <a class="minibutton select-menu-button js-menu-target" data-hotkey="w" data-ref=""> + <span class="mini-icon mini-icon-tree"></span> + <i>tree:</i> + <span class="js-select-button">d7504a3d7b</span> + </a> + + <div class="select-menu-modal-holder js-menu-content js-navigation-container"> + + <div class="select-menu-modal"> + <div class="select-menu-header"> + <span class="select-menu-title">Switch branches/tags</span> + <span class="mini-icon mini-icon-remove-close js-menu-close"></span> + </div> <!-- /.select-menu-header --> + + <div class="select-menu-filters"> + <div class="select-menu-text-filter"> + <input type="text" id="commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Find or create a branch…"> + </div> + <div class="select-menu-tabs"> + <ul> + <li class="select-menu-tab"> + <a href="#" data-tab-filter="branches" class="js-select-menu-tab">Branches</a> + </li> + <li class="select-menu-tab"> + <a href="#" data-tab-filter="tags" class="js-select-menu-tab">Tags</a> + </li> + </ul> + </div><!-- /.select-menu-tabs --> + </div><!-- /.select-menu-filters --> + + <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket css-truncate" data-tab-filter="branches"> + + <div data-filterable-for="commitish-filter-field" data-filterable-type="substring"> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/boulder-oracle-sprint" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/boulder-oracle-sprint" rel="nofollow" title="attic/boulder-oracle-sprint">attic/boulder-oracle-sprint</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/full-history" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/full-history" rel="nofollow" title="attic/full-history">attic/full-history</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/generic-auth" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/generic-auth" rel="nofollow" title="attic/generic-auth">attic/generic-auth</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/gis" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/gis" rel="nofollow" title="attic/gis">attic/gis</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/i18n" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/i18n" rel="nofollow" title="attic/i18n">attic/i18n</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/magic-removal" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/magic-removal" rel="nofollow" title="attic/magic-removal">attic/magic-removal</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/multi-auth" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/multi-auth" rel="nofollow" title="attic/multi-auth">attic/multi-auth</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/multiple-db-support" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/multiple-db-support" rel="nofollow" title="attic/multiple-db-support">attic/multiple-db-support</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/new-admin" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/new-admin" rel="nofollow" title="attic/new-admin">attic/new-admin</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/newforms-admin" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/newforms-admin" rel="nofollow" title="attic/newforms-admin">attic/newforms-admin</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/per-object-permissions" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/per-object-permissions" rel="nofollow" title="attic/per-object-permissions">attic/per-object-permissions</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/queryset-refactor" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/queryset-refactor" rel="nofollow" title="attic/queryset-refactor">attic/queryset-refactor</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/schema-evolution" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/schema-evolution" rel="nofollow" title="attic/schema-evolution">attic/schema-evolution</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/schema-evolution-ng" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/schema-evolution-ng" rel="nofollow" title="attic/schema-evolution-ng">attic/schema-evolution-ng</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/search-api" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/search-api" rel="nofollow" title="attic/search-api">attic/search-api</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/sqlalchemy" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/sqlalchemy" rel="nofollow" title="attic/sqlalchemy">attic/sqlalchemy</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/attic/unicode" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="attic/unicode" rel="nofollow" title="attic/unicode">attic/unicode</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/master" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="master" rel="nofollow" title="master">master</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/admin-ui" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/admin-ui" rel="nofollow" title="soc2009/admin-ui">soc2009/admin-ui</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/http-wsgi-improvements" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/http-wsgi-improvements" rel="nofollow" title="soc2009/http-wsgi-improvements">soc2009/http-wsgi-improvements</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/i18n-improvements" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/i18n-improvements" rel="nofollow" title="soc2009/i18n-improvements">soc2009/i18n-improvements</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/model-validation" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/model-validation" rel="nofollow" title="soc2009/model-validation">soc2009/model-validation</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/multidb" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/multidb" rel="nofollow" title="soc2009/multidb">soc2009/multidb</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2009/test-improvements" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2009/test-improvements" rel="nofollow" title="soc2009/test-improvements">soc2009/test-improvements</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2010/app-loading" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2010/app-loading" rel="nofollow" title="soc2010/app-loading">soc2010/app-loading</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2010/query-refactor" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2010/query-refactor" rel="nofollow" title="soc2010/query-refactor">soc2010/query-refactor</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/soc2010/test-refactor" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="soc2010/test-refactor" rel="nofollow" title="soc2010/test-refactor">soc2010/test-refactor</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.90.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.90.x" rel="nofollow" title="stable/0.90.x">stable/0.90.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.91.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.91.x" rel="nofollow" title="stable/0.91.x">stable/0.91.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.95.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.95.x" rel="nofollow" title="stable/0.95.x">stable/0.95.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/0.96.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/0.96.x" rel="nofollow" title="stable/0.96.x">stable/0.96.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.0.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.0.x" rel="nofollow" title="stable/1.0.x">stable/1.0.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.1.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.1.x" rel="nofollow" title="stable/1.1.x">stable/1.1.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.2.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.2.x" rel="nofollow" title="stable/1.2.x">stable/1.2.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.3.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.3.x" rel="nofollow" title="stable/1.3.x">stable/1.3.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.4.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.4.x" rel="nofollow" title="stable/1.4.x">stable/1.4.x</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/stable/1.5.x" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="stable/1.5.x" rel="nofollow" title="stable/1.5.x">stable/1.5.x</a> + </div> <!-- /.select-menu-item --> + </div> + + <form accept-charset="UTF-8" action="/django/django/branches" class="js-create-branch select-menu-item select-menu-new-item-form js-navigation-item js-navigation-target js-new-item-form" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> + + <span class="mini-icon mini-icon-branch-create select-menu-item-icon"></span> + <div class="select-menu-item-text"> + <h4>Create branch: <span class="js-new-item-name"></span></h4> + <span class="description">from ‘d7504a3’</span> + </div> + <input type="hidden" name="name" id="name" class="js-new-item-submit" /> + <input type="hidden" name="branch" id="branch" value="d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" /> + <input type="hidden" name="path" id="branch" value="" /> + </form> <!-- /.select-menu-item --> + + </div> <!-- /.select-menu-list --> + + + <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket css-truncate" data-tab-filter="tags"> + <div data-filterable-for="commitish-filter-field" data-filterable-type="substring"> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5c2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5c2" rel="nofollow" title="1.5c2">1.5c2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5c1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5c1" rel="nofollow" title="1.5c1">1.5c1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5b2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5b2" rel="nofollow" title="1.5b2">1.5b2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5b1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5b1" rel="nofollow" title="1.5b1">1.5b1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5a1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5a1" rel="nofollow" title="1.5a1">1.5a1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5.1" rel="nofollow" title="1.5.1">1.5.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.5" rel="nofollow" title="1.5">1.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.5" rel="nofollow" title="1.4.5">1.4.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.4" rel="nofollow" title="1.4.4">1.4.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.3" rel="nofollow" title="1.4.3">1.4.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.2" rel="nofollow" title="1.4.2">1.4.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4.1" rel="nofollow" title="1.4.1">1.4.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.4" rel="nofollow" title="1.4">1.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.7" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.7" rel="nofollow" title="1.3.7">1.3.7</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.6" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.6" rel="nofollow" title="1.3.6">1.3.6</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.5" rel="nofollow" title="1.3.5">1.3.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.4" rel="nofollow" title="1.3.4">1.3.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.3" rel="nofollow" title="1.3.3">1.3.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.2" rel="nofollow" title="1.3.2">1.3.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3.1" rel="nofollow" title="1.3.1">1.3.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.3" rel="nofollow" title="1.3">1.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.7" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.7" rel="nofollow" title="1.2.7">1.2.7</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.6" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.6" rel="nofollow" title="1.2.6">1.2.6</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.5" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.5" rel="nofollow" title="1.2.5">1.2.5</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.4" rel="nofollow" title="1.2.4">1.2.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.3" rel="nofollow" title="1.2.3">1.2.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.2" rel="nofollow" title="1.2.2">1.2.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2.1" rel="nofollow" title="1.2.1">1.2.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.2" rel="nofollow" title="1.2">1.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.4" rel="nofollow" title="1.1.4">1.1.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.3" rel="nofollow" title="1.1.3">1.1.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.2" rel="nofollow" title="1.1.2">1.1.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1.1" rel="nofollow" title="1.1.1">1.1.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.1" rel="nofollow" title="1.1">1.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.4" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.4" rel="nofollow" title="1.0.4">1.0.4</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.3" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.3" rel="nofollow" title="1.0.3">1.0.3</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.2" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.2" rel="nofollow" title="1.0.2">1.0.2</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0.1" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0.1" rel="nofollow" title="1.0.1">1.0.1</a> + </div> <!-- /.select-menu-item --> + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <a href="/django/django/commit/1.0" class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target" data-name="1.0" rel="nofollow" title="1.0">1.0</a> + </div> <!-- /.select-menu-item --> + </div> + + <div class="select-menu-no-results">Nothing to show</div> + + </div> <!-- /.select-menu-list --> + + </div> <!-- /.select-menu-modal --> + </div> <!-- /.select-menu-modal-holder --> + </div> <!-- /.select-menu --> + + </div> <!-- /.scope --> + + <ul class="tabnav-tabs"> + <li><a href="/django/django" class="tabnav-tab" highlight="repo_source">Files</a></li> + <li><a href="/django/django/commits/" class="selected tabnav-tab" highlight="repo_commits">Commits</a></li> + <li><a href="/django/django/branches" class="tabnav-tab" highlight="repo_branches" rel="nofollow">Branches <span class="counter ">37</span></a></li> + </ul> + +</div> + + + + + + + + </div> + </div><!-- /.repohead --> + + <div id="js-repo-pjax-container" class="container context-loader-container" data-pjax-container> + + + + <div class="commit full-commit "> + <a href="/django/django/tree/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" class="browse-button" title="Browse the code at this point in the history" rel="nofollow">Browse code</a> + + <p class="commit-title"> + Improved regex in strip_tags + </p> + + <div class="commit-desc"><pre>Thanks Pablo Recio for the report. Refs #19237.</pre></div> + + <div class="commit-meta clearfix"> + <span class="sha-block">commit <span class="sha js-selectable-text">d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e</span></span> + + <span class="sha-block" data-pjax> + 1 parent + + <a href="/django/django/commit/afa3e1633431137f4e76c7efc359b579f4d9c08e" class="sha" data-hotkey="p">afa3e16</a> + </span> + + <div class="authorship"> + <img class="gravatar" height="24" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="24" /> + <span class="author-name"><a href="/claudep" rel="author">claudep</a></span> + authored <time class="js-relative-date" datetime="2013-02-06T12:20:43-08:00" title="2013-02-06 12:20:43">February 06, 2013</time> + + </div> + </div> + </div> + + <a name="diff-stat"></a> + +<div id='toc' class="details-collapse js-details-container "> + <p class="explain"> + <span class="mini-icon mini-icon-diff"></span>Showing <strong>2 changed files</strong> + with <strong>2 additions</strong> + and <strong>1 deletion</strong>. + <a href="#" class="minibutton show-diff-stats js-details-target">Show Diff Stats</a> + <a href="#" class="minibutton hide-diff-stats js-details-target">Hide Diff Stats</a></p> + + <ol class="content collapse js-transitionable"> + <li> + <span class="diffstat"> + <a href="#diff-0" class="tooltipped leftwards" title="1 addition & 1 deletion"> + 2 + <span class="diffstat-bar"> <i class='plus'></i><i class='minus'></i> </span> + </a> + </span> + <span class='mini-icon mini-icon-modified' title='modified'></span> <a href="#diff-0">django/utils/html.py</a> + </li> + <li> + <span class="diffstat"> + <a href="#diff-1" class="tooltipped leftwards" title="1 addition & 0 deletions"> + 1 + <span class="diffstat-bar"> <i class='plus'></i> </span> + </a> + </span> + <span class='mini-icon mini-icon-modified' title='modified'></span> <a href="#diff-1">tests/regressiontests/utils/html.py</a> + </li> + </ol> +</div> + + +<div id="files" class="diff-view commentable"> + <div id="diff-0" class="file js-details-container"> + <div class="meta" data-path="django/utils/html.py"> + <div class="info"> + <span class="diffstat tooltipped rightwards" title="1 addition & 1 deletion">2 <span class="diffstat-bar"><i class='plus'></i><i class='minus'></i></span></span> + + <span class="js-selectable-text css-truncate css-truncate-target" title="django/utils/html.py"> + django/utils/html.py + </span> + + </div> + <div class="actions"> + <span class="show-inline-notes"> + <label> + <input type="checkbox" checked="checked" class="js-show-inline-comments-toggle"> + show inline notes + </label> + </span> + <div class="button-group"> + <a href="/django/django/blob/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e/django/utils/html.py" class="minibutton" rel="nofollow">View file @ <code>d7504a3</code></a> + </div> + </div> + </div> + + <div class="data highlight "> + <table class="diff-table"> + <tr id="django-utils-html-py-P0" data-position='0'> + <td id="L0L32" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td id="L0R32" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td class="gc diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=0&line=32"></b> + @@ -33,7 +33,7 @@ + </td> + </tr> + <tr id="django-utils-html-py-P1" data-position='1'> + <td id="L0L33" class="line_numbers linkable-line-number"> + <span class="line-number-content">33</span> + </td> + <td id="L0R33" class="line_numbers linkable-line-number"> + <span class="line-number-content">33</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=1&line=33"></b> + html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE) + </td> + </tr> + <tr id="django-utils-html-py-P2" data-position='2'> + <td id="L0L34" class="line_numbers linkable-line-number"> + <span class="line-number-content">34</span> + </td> + <td id="L0R34" class="line_numbers linkable-line-number"> + <span class="line-number-content">34</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=2&line=34"></b> + hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL) + </td> + </tr> + <tr id="django-utils-html-py-P3" data-position='3'> + <td id="L0L35" class="line_numbers linkable-line-number"> + <span class="line-number-content">35</span> + </td> + <td id="L0R35" class="line_numbers linkable-line-number"> + <span class="line-number-content">35</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=3&line=35"></b> + trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z') + </td> + </tr> + <tr id="django-utils-html-py-P4" data-position='4'> + <td id="L0L36" class="line_numbers linkable-line-number"> + <span class="line-number-content">36</span> + </td> + <td id="L0R35" class="line_numbers linkable-line-number empty-cell"> + <span class="line-number-content"> </span> + </td> + <td class="gd diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=4&line=36"></b> + -strip_tags_re = re.compile(r'</?\S([^=<span class="x"></span>]*=(\s*"[^"]*"|\s*\'[^\']*\'|\S*)|[^>])*?>', re.IGNORECASE) + </td> + </tr> + <tr id="django-utils-html-py-P5" data-position='5'> + <td id="L0L36" class="line_numbers linkable-line-number empty-cell"> + <span class="line-number-content"> </span> + </td> + <td id="L0R36" class="line_numbers linkable-line-number"> + <span class="line-number-content">36</span> + </td> + <td class="gi diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=5&line=36"></b> + +strip_tags_re = re.compile(r'</?\S([^=<span class="x">></span>]*=(\s*"[^"]*"|\s*\'[^\']*\'|\S*)|[^>])*?>', re.IGNORECASE) + </td> + </tr> + <tr id="django-utils-html-py-P6" data-position='6'> + <td id="L0L37" class="line_numbers linkable-line-number"> + <span class="line-number-content">37</span> + </td> + <td id="L0R37" class="line_numbers linkable-line-number"> + <span class="line-number-content">37</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=6&line=37"></b> + + </td> + </tr> + <tr id="django-utils-html-py-P7" data-position='7'> + <td id="L0L38" class="line_numbers linkable-line-number"> + <span class="line-number-content">38</span> + </td> + <td id="L0R38" class="line_numbers linkable-line-number"> + <span class="line-number-content">38</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=7&line=38"></b> + + </td> + </tr> + <tr id="django-utils-html-py-P8" data-position='8'> + <td id="L0L39" class="line_numbers linkable-line-number"> + <span class="line-number-content">39</span> + </td> + <td id="L0R39" class="line_numbers linkable-line-number"> + <span class="line-number-content">39</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=django/utils/html.py&position=8&line=39"></b> + def escape(text): + </td> + </tr> + </table> + </div> + + <div class="file-comments-place-holder" data-path="django/utils/html.py"></div> + </div> + <div id="diff-1" class="file js-details-container"> + <div class="meta" data-path="tests/regressiontests/utils/html.py"> + <div class="info"> + <span class="diffstat tooltipped rightwards" title="1 addition & 0 deletions">1 <span class="diffstat-bar"><i class='plus'></i></span></span> + + <span class="js-selectable-text css-truncate css-truncate-target" title="tests/regressiontests/utils/html.py"> + tests/regressiontests/utils/html.py + </span> + + </div> + <div class="actions"> + <span class="show-inline-notes"> + <label> + <input type="checkbox" checked="checked" class="js-show-inline-comments-toggle"> + show inline notes + </label> + </span> + <div class="button-group"> + <a href="/django/django/blob/d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e/tests/regressiontests/utils/html.py" class="minibutton" rel="nofollow">View file @ <code>d7504a3</code></a> + </div> + </div> + </div> + + <div class="data highlight "> + <table class="diff-table"> + <tr id="tests-regressiontests-utils-html-py-P0" data-position='0'> + <td id="L1L67" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td id="L1R67" class="line_numbers linkable-line-number"> + <span class="line-number-content">...</span> + </td> + <td class="gc diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=0&line=67"></b> + @@ -68,6 +68,7 @@ def test_strip_tags(self): + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P1" data-position='1'> + <td id="L1L68" class="line_numbers linkable-line-number"> + <span class="line-number-content">68</span> + </td> + <td id="L1R68" class="line_numbers linkable-line-number"> + <span class="line-number-content">68</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=1&line=68"></b> + ('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P2" data-position='2'> + <td id="L1L69" class="line_numbers linkable-line-number"> + <span class="line-number-content">69</span> + </td> + <td id="L1R69" class="line_numbers linkable-line-number"> + <span class="line-number-content">69</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=2&line=69"></b> + ('a<p a >b</p>c', 'abc'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P3" data-position='3'> + <td id="L1L70" class="line_numbers linkable-line-number"> + <span class="line-number-content">70</span> + </td> + <td id="L1R70" class="line_numbers linkable-line-number"> + <span class="line-number-content">70</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=3&line=70"></b> + ('d<a:b c:d>e</p>f', 'def'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P4" data-position='4'> + <td id="L1L70" class="line_numbers linkable-line-number empty-cell"> + <span class="line-number-content"> </span> + </td> + <td id="L1R71" class="line_numbers linkable-line-number"> + <span class="line-number-content">71</span> + </td> + <td class="gi diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=4&line=71"></b> + + ('<strong>foo</strong><a href="http://example.com">bar</a>', 'foobar'), + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P5" data-position='5'> + <td id="L1L71" class="line_numbers linkable-line-number"> + <span class="line-number-content">71</span> + </td> + <td id="L1R72" class="line_numbers linkable-line-number"> + <span class="line-number-content">72</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=5&line=72"></b> + ) + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P6" data-position='6'> + <td id="L1L72" class="line_numbers linkable-line-number"> + <span class="line-number-content">72</span> + </td> + <td id="L1R73" class="line_numbers linkable-line-number"> + <span class="line-number-content">73</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=6&line=73"></b> + for value, output in items: + </td> + </tr> + <tr id="tests-regressiontests-utils-html-py-P7" data-position='7'> + <td id="L1L73" class="line_numbers linkable-line-number"> + <span class="line-number-content">73</span> + </td> + <td id="L1R74" class="line_numbers linkable-line-number"> + <span class="line-number-content">74</span> + </td> + <td class=" diff-line line"> + <b class="add-bubble mini-icon mini-icon-add-comment" data-remote="/django/django/commit_comment/form?commit_id=d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e&path=tests/regressiontests/utils/html.py&position=7&line=74"></b> + self.check_output(f, value, output) + </td> + </tr> + </table> + </div> + + <div class="file-comments-place-holder" data-path="tests/regressiontests/utils/html.py"></div> + </div> +</div> + + +<div id="all_commit_comments"> + + + <h2 class="commit-comments-header"> + 0 notes + on commit <code class="commit-comments-header-sha">d7504a3</code> + <span class="commit-comments-toggle-line-notes-wrapper"><label><input id="js-inline-comments-toggle" class="commit-comments-toggle-line-notes" type="checkbox">Show line notes below</label></span> + </h2> + <div id="comments" class="only-commit-comments comment-holder commit-comments"> + </div> + + <div class="discussion-bubble js-comment-container"> + <img class="discussion-bubble-avatar" height="48" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="48" /> + + <form accept-charset="UTF-8" action="/django/django/commit_comment/create" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> + <div class="discussion-bubble-content bubble"> + <div class="discussion-bubble-inner"> + <input type='hidden' name='commit_id' value='d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e'> + + +<div class="js-previewable-comment-form previewable-comment-form write-selected" data-preview-url="/preview?repository=4164482"> + <div class="comment-form-head tabnav"> + <ul class="tabnav-tabs"> + <li><a href="#write_bucket_525" class="tabnav-tab write-tab js-write-tab selected">Write</a></li> + <li><a href="#preview_bucket_525" class="tabnav-tab preview-tab js-preview-tab">Preview</a></li> + </ul> + <span class="tabnav-right"> + <span class="tabnav-widget text">Comments are parsed with <a href="http://github.github.com/github-flavored-markdown/" class="gfm-help" target="_blank">GitHub Flavored Markdown</a></span> + </span> + </div> + <div class="comment-form-error js-comment-form-error" style="display:none;"></div> + + <div id="write_bucket_525" class="write-content js-write-bucket js-uploadable-container upload-enabled is-default" data-model="asset"> + +<a href="#fullscreen_comment_body_525" class="enable-fullscreen js-enable-fullscreen tooltipped + leftwards " title="Zen Mode"> + <span class="mini-icon mini-icon-fullscreen"></span> +</a> + + <textarea name="comment[body]" + tabindex="2" + id="comment_body_525" + placeholder="Leave a comment" + class="js-comment-field js-size-to-fit input-with-fullscreen-icon" + data-suggester="525_new_preview_suggester" + required></textarea> + + + <p class="drag-and-drop"> + <span class="default"> + Attach images by dragging & dropping them or + <input type="file" multiple="multiple" class="manual-file-chooser js-manual-file-chooser"> + <a class="manual-file-chooser-text" href="#">choose an image</a> + </span> + <span class="loading"> + <img alt="Octocat-spinner-32" height="16" src="https://a248.e.akamai.net/assets.github.com/images/spinners/octocat-spinner-32.gif?1338945075" width="16" /> Uploading your images now… + </span> + <span class="error bad-file"> + Unfortunately we don't support that file type yet. Try image files less than 5MB. + </span> + <span class="error bad-browser"> + This browser doesn't support image attachments. + </span> + <span class="error failed-request"> + Something went really wrong and we can't process that image. + </span> + </p> + + </div> + <div id="preview_bucket_525" class="preview-content js-preview-bucket"> + +<div id="openstruct-163168300" class="js-comment comment"> + <div class="comment-header normal-comment-header"> + <img class="comment-header-gravatar" height="22" src="https://secure.gravatar.com/avatar/cf4198670f0073174b475634964b576b?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="22" /> + <a href="/claudep" class="comment-header-author">claudep</a> + <span class='comment-header-action-text'> + <a href="#openstruct-163168300"> + commented + </a> + </span> + + <span class="comment-header-right"> + <a href="#openstruct-163168300" class="comment-header-date"><time class="js-relative-date" datetime="2013-04-01T06:45:26-07:00" title="2013-04-01 06:45:26">April 01, 2013</time></a> + </span> + </div> <!-- /.comment-header --> + + + <div class="comment-content"> + <div class="edit-comment-hide"> + <div class="js-comment-body comment-body markdown-body " data-body-version=""> + <p>Nothing to preview</p> + </div> + </div> + </div> <!-- /.comment-content --> +</div> <!-- /.comment --> + + </div> + + <div class="suggester-container"> + <div class="suggester js-navigation-container" id="525_new_preview_suggester" + data-url="/django/django/suggestions/commit"> + </div> + </div> +</div> + + </div> + </div> + + <div class="form-actions"> + <div class="tip"> + <img alt="Commit_comment_tip" height="35" src="https://a248.e.akamai.net/assets.github.com/images/modules/commit/commit_comment_tip.gif?1347524281" width="95" /> + <p><strong>Tip:</strong> You can also add notes to lines in a file. Hover to the left of a line to make a note</p> + </div> + <button type="submit" class="button primary" tabindex="2" data-disable-invalid data-disable-with>Comment on this commit</button> + </div> +</form> </div><!-- /.discussion-bubble --> + + + +</div> + + + +<div class="thread-subscription-status clearfix"> + <span class="mega-icon mega-icon-notifications"></span> + + <div class="select-menu js-menu-container js-select-menu"> + + <form accept-charset="UTF-8" action="/notifications/thread" data-autosubmit="true" data-remote="true" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="Vbmuc30dLLFdm7POIe3xfTa4nODYc/la/wLrI1OLEOI=" /></div> <input id="repository_id" name="repository_id" type="hidden" value="4164482" /> + <input id="thread_id" name="thread_id" type="hidden" value="d7504a3d7b8645bdb979bab7ded0e9a9b6dccd0e" /> + <input id="thread_class" name="thread_class" type="hidden" value="c" /> + + <span class="minibutton select-menu-button js-menu-target"> + <span class="js-select-button"> + <span class="mini-icon mini-icon-watching"></span> + Watch thread + </span> + </span> + + <div class="select-menu-modal-holder js-menu-content js-navigation-container"> + <div class="select-menu-modal"> + <div class="select-menu-header"> + <span class="select-menu-title">Thread notifications</span> + <span class="mini-icon mini-icon-remove-close js-menu-close"></span> + </div> <!-- /.select-menu-header --> + + <div class="select-menu-list"> + + <div class="select-menu-item js-navigation-item js-navigation-target selected"> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="id_unsubscribe" name="id" type="radio" value="unsubscribe" /> + <h4>Not watching</h4> + <span class="description">You only receive notifications for this thread if you participate or are @mentioned.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-watching"></span> + Watch thread + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="id_subscribe" name="id" type="radio" value="subscribe" /> + <h4>Watching</h4> + <span class="description">Receive all notifications for this thread.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-unwatch"></span> + Unwatch thread + </span> + </div> + </div> <!-- /.select-menu-item --> + + <div class="select-menu-item js-navigation-item js-navigation-target "> + <span class="select-menu-item-icon mini-icon mini-icon-confirm"></span> + <div class="select-menu-item-text"> + <input checked="checked" id="id_mute" name="id" type="radio" value="mute" /> + <h4>Ignoring</h4> + <span class="description">You do not receive notifications for this thread.</span> + <span class="js-select-button-text hidden-select-button-text"> + <span class="mini-icon mini-icon-mute ignored"></span> + Stop ignoring thread + </span> + </div> + </div> <!-- /.select-menu-item --> + + </div> <!-- /.select-menu-list --> + </div> <!-- /.select-menu-modal --> + </div> <!-- /.select-menu-modal-holder --> +</form> </div> <!-- /.select-menu --> + + <p class="reason">You only receive notifications for this thread when you participate or are @mentioned.</p> +</div> + + + +<!-- COMMENTS --> +<div id='diff-comment-data' style='display:none'> +</div> + + + </div> + </div> + <div class="context-overlay"></div> + </div> + + <div id="footer-push"></div><!-- hack for sticky footer --> + </div><!-- end of wrapper - hack for sticky footer --> + + <!-- footer --> + <div id="footer"> + <div class="container clearfix"> + + <dl class="footer_nav"> + <dt>GitHub</dt> + <dd><a href="https://github.com/about">About us</a></dd> + <dd><a href="https://github.com/blog">Blog</a></dd> + <dd><a href="https://github.com/contact">Contact & support</a></dd> + <dd><a href="http://enterprise.github.com/">GitHub Enterprise</a></dd> + <dd><a href="http://status.github.com/">Site status</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>Applications</dt> + <dd><a href="http://mac.github.com/">GitHub for Mac</a></dd> + <dd><a href="http://windows.github.com/">GitHub for Windows</a></dd> + <dd><a href="http://eclipse.github.com/">GitHub for Eclipse</a></dd> + <dd><a href="http://mobile.github.com/">GitHub mobile apps</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>Services</dt> + <dd><a href="http://get.gaug.es/">Gauges: Web analytics</a></dd> + <dd><a href="http://speakerdeck.com">Speaker Deck: Presentations</a></dd> + <dd><a href="https://gist.github.com">Gist: Code snippets</a></dd> + <dd><a href="http://jobs.github.com/">Job board</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>Documentation</dt> + <dd><a href="http://help.github.com/">GitHub Help</a></dd> + <dd><a href="http://developer.github.com/">Developer API</a></dd> + <dd><a href="http://github.github.com/github-flavored-markdown/">GitHub Flavored Markdown</a></dd> + <dd><a href="http://pages.github.com/">GitHub Pages</a></dd> + </dl> + + <dl class="footer_nav"> + <dt>More</dt> + <dd><a href="http://training.github.com/">Training</a></dd> + <dd><a href="https://github.com/edu">Students & teachers</a></dd> + <dd><a href="http://shop.github.com">The Shop</a></dd> + <dd><a href="/plans">Plans & pricing</a></dd> + <dd><a href="http://octodex.github.com/">The Octodex</a></dd> + </dl> + + <hr class="footer-divider"> + + + <p class="right">© 2013 <span title="0.09734s from fe2.rs.github.com">GitHub</span>, Inc. All rights reserved.</p> + <a class="left" href="https://github.com/"> + <span class="mega-icon mega-icon-invertocat"></span> + </a> + <ul id="legal"> + <li><a href="https://github.com/site/terms">Terms of Service</a></li> + <li><a href="https://github.com/site/privacy">Privacy</a></li> + <li><a href="https://github.com/security">Security</a></li> + </ul> + + </div><!-- /.container --> + +</div><!-- /.#footer --> + + + <div class="fullscreen-overlay js-fullscreen-overlay" id="fullscreen_overlay"> + <div class="fullscreen-container js-fullscreen-container"> + <div class="textarea-wrap"> + <textarea name="fullscreen-contents" id="fullscreen-contents" class="js-fullscreen-contents" placeholder="" data-suggester="fullscreen_suggester"></textarea> + <div class="suggester-container"> + <div class="suggester fullscreen-suggester js-navigation-container" id="fullscreen_suggester" + data-url="/django/django/suggestions/commit"> + </div> + </div> + </div> + </div> + <div class="fullscreen-sidebar"> + <a href="#" class="exit-fullscreen js-exit-fullscreen tooltipped leftwards" title="Exit Zen Mode"> + <span class="mega-icon mega-icon-normalscreen"></span> + </a> + <a href="#" class="theme-switcher js-theme-switcher tooltipped leftwards" + title="Switch themes"> + <span class="mini-icon mini-icon-brightness"></span> + </a> + </div> +</div> + + + + <div id="ajax-error-message" class="flash flash-error"> + <span class="mini-icon mini-icon-exclamation"></span> + Something went wrong with that request. Please try again. + <a href="#" class="mini-icon mini-icon-remove-close ajax-error-dismiss"></a> + </div> + + + + <span id='server_response_time' data-time='0.09780' data-host='fe2'></span> + + </body> +</html> + diff --git a/tests/utils_tests/files/strip_tags2.txt b/tests/utils_tests/files/strip_tags2.txt new file mode 100644 index 0000000000..6b2a4033c2 --- /dev/null +++ b/tests/utils_tests/files/strip_tags2.txt @@ -0,0 +1,118 @@ +_**Prerequisite**: You are already aware of the [basics of building a HelloWorld](http://developer.android.com/training/index.html) in Android and know [how to use the APIs provided in the support library](http://developer.android.com/training/basics/fragments/support-lib.html)._ + +_The code example is available on [github](http://github.com/iontech/Fragments_Example "Fragments Example")._ +_____________________________________________________________ +Ever wanted a code snippet from an Activity to be available to other activities? Perhaps a Button or a ListView, maybe a Layout or any View/ViewGroup for that matter? Fragments let us do just that. + +Necessity is the mother of invention. +Before understanding what Fragments are and how they work, we must first realize their existence in the first place. + +The Problem +----------- +Suppose we have an Android app with two Activities- [*FirstActivity*](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/FirstActivity.java) and [*SecondActivity*](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/SecondActivity.java). +*FirstActivity* contains two Views, a `TextView` (*textView*) and a `Button` (*button1*); and *button1* has an `onClick()` callback that `Toast`'s a simple message "Button pressed". +*SecondActivity* contains both the Views present in *FirstActivity* and a `Button` (*button2*). + +Now we want to utilize the two layout components(Views) of *FirstActivity* in *SecondActivity*, we can go about this with two approaches: + +1. Copy and Paste the xml elements of the two Views. +2. Create a separate layout for common Views and reuse it using `` layout element. + More about this [here](http://developer.android.com/training/improving-layouts/reusing-layouts.html). + +Electing the second approach makes sense cause it enables us to make reusable layouts. Everything seems great till now. We are able to make reusable layouts and use them as many times as we want. + +Now recollect that we have an `onClick()` callback assigned to *button1*. How do we reuse the same callback functionality of *button1* across multiple Activities? `` lets us reuse layouts and not the Activity source. +This is where Fragments come into play. + +Fragments +--------- +<center>  </center> +Fragments encompass both layout resource and Java source. Hence, unlike ``, they allow us to reuse the View components along with their functionality, if needed. +Fragments were first introduced in Honeycomb(API 11), living under the `android.app` package. +**Note**: API 11 implies that Fragments have no support for devices less than Honeycomb and, for the record, as of writing this post, [more than 50% of Android devices worldwide run versions of Android below Honeycomb](http://developer.android.com/about/dashboards/index.html). Developer dissapointed? You don't have to be, cause google has been cautious enough to add the Fragment APIs to the support library. Yay! + +In the support library Fragment APIs sit in the `android.support.v4.app` package. This post assumes that your `minSdk` support is below API 11. Hence we concentrate on the Fragment APIs of the support library. + +### Diving into code + +Performing code reuse with Fragments involves three major steps: + +1. Creating reusable View components - Creating a layout for the fragment. +2. Creating reusable Java source - Writing the layout's corresponding Fragment class. +3. Employing the reusable components in Activity - Making an Activity to host this Fragment. + +#### 1. Creating reusable View components +##### Creating a layout for the Fragment +This is done precisely as we do it for our activity layouts. The layout contains a root element (ViewGroup) defining the layout, For instance in our example we use a LinearLayout and its child elements(the reusable Views) that we want to have in our fragment. + +> [fragment_common.xml](https://github.com/iontech/Fragments_Example/blob/master/res/layout/fragment_common.xml) + + + + + + <button> + + + +#### 2. Creating reusable Java source +##### Writing the layout's corresponding Fragment class + +> [CommonFragment.java](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/CommonFragment.java) + +This class will inherit `Fragment` class and must override the `onCreateView()` method. +In this method we inflate the fragment layout using the following line of code. + + View view = inflater.inflate(R.layout.fragment_common, container, false); + +*container* is the parent ViewGroup that the fragment's UI should be attached to. +Once inflation is done, we can perform various operations on the component views of the fragment. +Accessing the view elements from the layout is done exactly as we do in an Activity (using `findViewById()`) except that we use the `View` for the fragment's UI or an instance of the host Activity. + +In Activity we access Views(a Button for our example) as follows + + Button button1 = (Button) findViewById(R.id.button1); +In Fragment, we need to use the inflated view(if in `onCreateView()`) or get the instance of the host activity and access the views through this instance(when in a [lifecycle callback](http://developer.android.com/guide/components/fragments.html#Lifecycle "Fragment lifecycle callbacks") after `onCreateView()`, I generally do it in `onActivityCreated()`). + + Button button1 = (Button) view.findViewById(R.id.button1); +or + + Button button1 = (Button) getActivity().findViewById(R.id.button1); +`getActivity()` returns the instance of the Activity that is hosting this Fragment. + +Finally, in `onCreateView()` we must return the View for the fragment's UI. + + return view; + +#### 3. Employing the reusable components in Activity +##### Making an Activity to host this Fragment +This is done in two ways, statically by adding `` elements into the Activity layout or dynamically, at run time, by using `FragmentTransaction`s. +**Note**: First thing we need to ensure is that our Activity extends `FragmentActivity` class instead of the regular `Activity`. +If the `minSdk` is API 11 or higher, then we can leave our inheritance to `Activity` class and not bother about `FragmentActivity`. + +##### a. Static approach to hosting the Fragments +###### Adding `` element in activity layout + +> [activity_static.xml](https://github.com/iontech/Fragments_Example/blob/master/res/layout/activity_static.xml) + +Inorder to statically add a Fragment into your Activity, just add `` element with the necessary layout attributes and the `android:name` attribute set to the fully qualified class name of the corresponding Fragment. + + +##### b. Dynamic approach +###### Using `FragmentTransaction`s + +> [ADynamicFragmentActivity.java](https://github.com/iontech/Fragments_Example/blob/master/src/main/java/com/github/iontech/fragments_example/ADynamicFragmentActivity.java) + +Can be done using `FragmentManager` and `FragmentTransaction` classes. We call `add()`, in our FragmentActivity implementation, on an instance of `FragmentTransaction` to add a fragment to the host Activity. But that is not enough to show the fragment on the screen, i.e. the FragmentTransaction is not complete. We must call `commit()` to finish the transaction. + + CommonFragment fragment = new CommonFragment(); + FragmentManager manager = getSupportFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + transaction.add(R.id.dynamicFragmentLayout, fragment); + transaction.commit(); + +Similarly Fragments can be removed(`remove()`) as well as replaced(`replace()`) from the activity all at runtime. + +Congratulations, now you can use Fragments to write reusable code and easily host them over multiple activities. + +Please try again. Till next time. diff --git a/tests/utils_tests/models.py b/tests/utils_tests/models.py new file mode 100644 index 0000000000..de8f9e3803 --- /dev/null +++ b/tests/utils_tests/models.py @@ -0,0 +1,13 @@ +from django.db import models + + +class Category(models.Model): + name = models.CharField(max_length=100) + + def next(self): + return self + + +class Thing(models.Model): + name = models.CharField(max_length=100) + category = models.ForeignKey(Category) diff --git a/tests/regressiontests/utils/archive.py b/tests/utils_tests/test_archive.py index 8861b4a577..8861b4a577 100644 --- a/tests/regressiontests/utils/archive.py +++ b/tests/utils_tests/test_archive.py diff --git a/tests/regressiontests/utils/baseconv.py b/tests/utils_tests/test_baseconv.py index cc413b4e8e..cc413b4e8e 100644 --- a/tests/regressiontests/utils/baseconv.py +++ b/tests/utils_tests/test_baseconv.py diff --git a/tests/regressiontests/utils/checksums.py b/tests/utils_tests/test_checksums.py index cee6dca2a8..cee6dca2a8 100644 --- a/tests/regressiontests/utils/checksums.py +++ b/tests/utils_tests/test_checksums.py diff --git a/tests/regressiontests/utils/crypto.py b/tests/utils_tests/test_crypto.py index 5cf2934ab0..5cf2934ab0 100644 --- a/tests/regressiontests/utils/crypto.py +++ b/tests/utils_tests/test_crypto.py diff --git a/tests/regressiontests/utils/datastructures.py b/tests/utils_tests/test_datastructures.py index f65fc5c770..3161c04f97 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/utils_tests/test_datastructures.py @@ -139,14 +139,14 @@ class SortedDictTests(SimpleTestCase): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") d.insert(0, "hello", "world") - assert w[0].category is PendingDeprecationWarning + assert w[0].category is DeprecationWarning def test_value_for_index(self): d = SortedDict({"a": 3}) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") self.assertEqual(d.value_for_index(0), 3) - assert w[0].category is PendingDeprecationWarning + assert w[0].category is DeprecationWarning class MergeDictTests(SimpleTestCase): diff --git a/tests/regressiontests/utils/dateformat.py b/tests/utils_tests/test_dateformat.py index 0f4fd67f6f..78cedaf764 100644 --- a/tests/regressiontests/utils/dateformat.py +++ b/tests/utils_tests/test_dateformat.py @@ -14,6 +14,7 @@ class DateFormatTests(unittest.TestCase): def setUp(self): self.old_TZ = os.environ.get('TZ') os.environ['TZ'] = 'Europe/Copenhagen' + self._orig_lang = translation.get_language() translation.activate('en-us') try: @@ -25,6 +26,7 @@ class DateFormatTests(unittest.TestCase): self.tz_tests = False def tearDown(self): + translation.activate(self._orig_lang) if self.old_TZ is None: del os.environ['TZ'] else: diff --git a/tests/regressiontests/utils/dateparse.py b/tests/utils_tests/test_dateparse.py index 1a6ca646b8..1a6ca646b8 100644 --- a/tests/regressiontests/utils/dateparse.py +++ b/tests/utils_tests/test_dateparse.py diff --git a/tests/regressiontests/utils/datetime_safe.py b/tests/utils_tests/test_datetime_safe.py index c9c34dbf40..c9c34dbf40 100644 --- a/tests/regressiontests/utils/datetime_safe.py +++ b/tests/utils_tests/test_datetime_safe.py diff --git a/tests/regressiontests/utils/decorators.py b/tests/utils_tests/test_decorators.py index 4d503df026..4d503df026 100644 --- a/tests/regressiontests/utils/decorators.py +++ b/tests/utils_tests/test_decorators.py diff --git a/tests/regressiontests/utils/encoding.py b/tests/utils_tests/test_encoding.py index d191845518..7aaba25a7a 100644 --- a/tests/regressiontests/utils/encoding.py +++ b/tests/utils_tests/test_encoding.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.utils import unittest -from django.utils.encoding import force_bytes +from django.utils.encoding import force_bytes, filepath_to_uri class TestEncodingUtils(unittest.TestCase): @@ -15,3 +15,9 @@ class TestEncodingUtils(unittest.TestCase): exc = ValueError(error_msg) result = force_bytes(exc) self.assertEqual(result, error_msg.encode('utf-8')) + + def test_filepath_to_uri(self): + self.assertEqual(filepath_to_uri('upload\\чубака.mp4'), + 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4') + self.assertEqual(filepath_to_uri('upload\\чубака.mp4'.encode('utf-8')), + 'upload/%D1%87%D1%83%D0%B1%D0%B0%D0%BA%D0%B0.mp4') diff --git a/tests/regressiontests/utils/feedgenerator.py b/tests/utils_tests/test_feedgenerator.py index bcd53bb2a0..bcd53bb2a0 100644 --- a/tests/regressiontests/utils/feedgenerator.py +++ b/tests/utils_tests/test_feedgenerator.py diff --git a/tests/regressiontests/utils/functional.py b/tests/utils_tests/test_functional.py index 90a6f08630..3bb50007c6 100644 --- a/tests/regressiontests/utils/functional.py +++ b/tests/utils_tests/test_functional.py @@ -1,5 +1,5 @@ from django.utils import unittest -from django.utils.functional import lazy, lazy_property +from django.utils.functional import lazy, lazy_property, cached_property class FunctionalTestCase(unittest.TestCase): @@ -37,3 +37,30 @@ class FunctionalTestCase(unittest.TestCase): self.assertRaises(NotImplementedError, lambda: A().do) self.assertEqual(B().do, 'DO IT') + + def test_cached_property(self): + """ + Test that cached_property caches its value, + and that it behaves like a property + """ + + class A(object): + + @cached_property + def value(self): + return 1, object() + + a = A() + + # check that it is cached + self.assertEqual(a.value, a.value) + + # check that it returns the right thing + self.assertEqual(a.value[0], 1) + + # check that state isn't shared between instances + a2 = A() + self.assertNotEqual(a.value, a2.value) + + # check that it behaves like a property when there's no instance + self.assertIsInstance(A.value, cached_property) diff --git a/tests/regressiontests/utils/html.py b/tests/utils_tests/test_html.py index a0226c4765..090cc32d1c 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/utils_tests/test_html.py @@ -1,10 +1,14 @@ from __future__ import unicode_literals -import unittest +from datetime import datetime +import os from django.utils import html +from django.utils._os import upath +from django.utils.unittest import TestCase -class TestUtilsHtml(unittest.TestCase): + +class TestUtilsHtml(TestCase): def check_output(self, function, value, output=None): """ @@ -68,10 +72,22 @@ class TestUtilsHtml(unittest.TestCase): ('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'), ('a<p a >b</p>c', 'abc'), ('d<a:b c:d>e</p>f', 'def'), + ('<strong>foo</strong><a href="http://example.com">bar</a>', 'foobar'), ) for value, output in items: self.check_output(f, value, output) + # Test with more lengthy content (also catching performance regressions) + for filename in ('strip_tags1.html', 'strip_tags2.txt'): + path = os.path.join(os.path.dirname(upath(__file__)), 'files', filename) + with open(path, 'r') as fp: + start = datetime.now() + stripped = html.strip_tags(fp.read()) + elapsed = datetime.now() - start + self.assertEqual(elapsed.seconds, 0) + self.assertIn("Please try again.", stripped) + self.assertNotIn('<', stripped) + def test_strip_spaces_between_tags(self): f = html.strip_spaces_between_tags # Strings that should come out untouched. diff --git a/tests/regressiontests/utils/http.py b/tests/utils_tests/test_http.py index 6d3bc025af..6d3bc025af 100644 --- a/tests/regressiontests/utils/http.py +++ b/tests/utils_tests/test_http.py diff --git a/tests/regressiontests/utils/ipv6.py b/tests/utils_tests/test_ipv6.py index 1713de82b8..1713de82b8 100644 --- a/tests/regressiontests/utils/ipv6.py +++ b/tests/utils_tests/test_ipv6.py diff --git a/tests/utils_tests/test_itercompat.py b/tests/utils_tests/test_itercompat.py new file mode 100644 index 0000000000..02d3463370 --- /dev/null +++ b/tests/utils_tests/test_itercompat.py @@ -0,0 +1,11 @@ +from django.test import TestCase + +from .models import Category, Thing + + +class TestIsIterator(TestCase): + def test_regression(self): + """This failed on Django 1.5/Py2.6 because category has a next method.""" + category = Category.objects.create(name='category') + Thing.objects.create(category=category) + Thing.objects.filter(category=category) diff --git a/tests/regressiontests/utils/jslex.py b/tests/utils_tests/test_jslex.py index a2af7b4bcf..a2af7b4bcf 100644 --- a/tests/regressiontests/utils/jslex.py +++ b/tests/utils_tests/test_jslex.py diff --git a/tests/utils_tests/test_module/__init__.py b/tests/utils_tests/test_module/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/utils_tests/test_module/__init__.py diff --git a/tests/regressiontests/utils/test_module/bad_module.py b/tests/utils_tests/test_module/bad_module.py index cc0cd168ce..cc0cd168ce 100644 --- a/tests/regressiontests/utils/test_module/bad_module.py +++ b/tests/utils_tests/test_module/bad_module.py diff --git a/tests/regressiontests/utils/test_module/good_module.py b/tests/utils_tests/test_module/good_module.py index 0ca689832a..0ca689832a 100644 --- a/tests/regressiontests/utils/test_module/good_module.py +++ b/tests/utils_tests/test_module/good_module.py diff --git a/tests/regressiontests/utils/module_loading.py b/tests/utils_tests/test_module_loading.py index 3fc92b0862..0a905ef2f1 100644 --- a/tests/regressiontests/utils/module_loading.py +++ b/tests/utils_tests/test_module_loading.py @@ -3,9 +3,10 @@ import sys import imp from zipimport import zipimporter +from django.core.exceptions import ImproperlyConfigured from django.utils import unittest from django.utils.importlib import import_module -from django.utils.module_loading import module_has_submodule +from django.utils.module_loading import import_by_path, module_has_submodule from django.utils._os import upath @@ -18,35 +19,35 @@ class DefaultLoader(unittest.TestCase): def test_loader(self): "Normal module existence can be tested" - test_module = import_module('regressiontests.utils.test_module') + test_module = import_module('utils_tests.test_module') test_no_submodule = import_module( - 'regressiontests.utils.test_no_submodule') + 'utils_tests.test_no_submodule') # An importable child self.assertTrue(module_has_submodule(test_module, 'good_module')) - mod = import_module('regressiontests.utils.test_module.good_module') + mod = import_module('utils_tests.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') + self.assertRaises(ImportError, import_module, 'utils_tests.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') + self.assertRaises(ImportError, import_module, 'utils_tests.test_module.no_such_module') # A child that doesn't exist, but is the name of a package on the path self.assertFalse(module_has_submodule(test_module, 'django')) - self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.django') + self.assertRaises(ImportError, import_module, 'utils_tests.test_module.django') # Don't be confused by caching of import misses - import types # causes attempted import of regressiontests.utils.types - self.assertFalse(module_has_submodule(sys.modules['regressiontests.utils'], 'types')) + import types # causes attempted import of utils_tests.types + self.assertFalse(module_has_submodule(sys.modules['utils_tests'], 'types')) # A module which doesn't have a __path__ (so no submodules) self.assertFalse(module_has_submodule(test_no_submodule, 'anything')) self.assertRaises(ImportError, import_module, - 'regressiontests.utils.test_no_submodule.anything') + 'utils_tests.test_no_submodule.anything') class EggLoader(unittest.TestCase): def setUp(self): @@ -103,6 +104,33 @@ class EggLoader(unittest.TestCase): self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module') + +class ModuleImportTestCase(unittest.TestCase): + def test_import_by_path(self): + cls = import_by_path( + 'django.utils.module_loading.import_by_path') + self.assertEqual(cls, import_by_path) + + # Test exceptions raised + for path in ('no_dots_in_path', 'unexistent.path', + 'utils_tests.unexistent'): + self.assertRaises(ImproperlyConfigured, import_by_path, path) + + with self.assertRaises(ImproperlyConfigured) as cm: + import_by_path('unexistent.module.path', error_prefix="Foo") + self.assertTrue(str(cm.exception).startswith('Foo')) + + def test_import_error_traceback(self): + """Test preserving the original traceback on an ImportError.""" + try: + import_by_path('test_module.bad_module.content') + except ImproperlyConfigured: + traceback = sys.exc_info()[2] + + self.assertIsNotNone(traceback.tb_next.tb_next, + 'Should have more than the calling frame in the traceback.') + + class ProxyFinder(object): def __init__(self): self._cache = {} diff --git a/tests/regressiontests/utils/test_no_submodule.py b/tests/utils_tests/test_no_submodule.py index 3c424ac788..3c424ac788 100644 --- a/tests/regressiontests/utils/test_no_submodule.py +++ b/tests/utils_tests/test_no_submodule.py diff --git a/tests/regressiontests/utils/numberformat.py b/tests/utils_tests/test_numberformat.py index f9d9031e48..f9d9031e48 100644 --- a/tests/regressiontests/utils/numberformat.py +++ b/tests/utils_tests/test_numberformat.py diff --git a/tests/regressiontests/utils/os_utils.py b/tests/utils_tests/test_os_utils.py index a205d67431..a205d67431 100644 --- a/tests/regressiontests/utils/os_utils.py +++ b/tests/utils_tests/test_os_utils.py diff --git a/tests/regressiontests/utils/regex_helper.py b/tests/utils_tests/test_regex_helper.py index 41f4a4f85d..41f4a4f85d 100644 --- a/tests/regressiontests/utils/regex_helper.py +++ b/tests/utils_tests/test_regex_helper.py diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/utils_tests/test_simplelazyobject.py index 3f81e8f608..883e60aa81 100644 --- a/tests/regressiontests/utils/simplelazyobject.py +++ b/tests/utils_tests/test_simplelazyobject.py @@ -2,8 +2,8 @@ from __future__ import unicode_literals import copy import pickle +import sys -from django.test.utils import str_prefix from django.utils import six from django.utils.unittest import TestCase from django.utils.functional import SimpleLazyObject, empty @@ -59,10 +59,18 @@ class TestUtilsSimpleLazyObject(TestCase): 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.assertTrue("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) + # First, for an unevaluated SimpleLazyObject + x = SimpleLazyObject(complex_object) + # __repr__ contains __repr__ of setup function and does not evaluate + # the SimpleLazyObject + self.assertEqual("<SimpleLazyObject: %r>" % complex_object, repr(x)) + self.assertEqual(empty, x._wrapped) + + # Second, for an evaluated SimpleLazyObject + name = x.name # evaluate + self.assertTrue(isinstance(x._wrapped, _ComplexObject)) + # __repr__ contains __repr__ of wrapped object + self.assertEqual("<SimpleLazyObject: %r>" % x._wrapped, repr(x)) def test_bytes(self): self.assertEqual(b"I am _ComplexObject('joe')", @@ -89,14 +97,14 @@ class TestUtilsSimpleLazyObject(TestCase): self.assertEqual(s2, complex_object()) # Second, for an evaluated SimpleLazyObject - name = s.name # evaluate + name = s.name # evaluate self.assertIsNot(s._wrapped, empty) s3 = copy.deepcopy(s) self.assertEqual(s3, complex_object()) - def test_none(self): i = [0] + def f(): i[0] += 1 return None @@ -121,3 +129,26 @@ class TestUtilsSimpleLazyObject(TestCase): self.assertEqual(unpickled, x) self.assertEqual(six.text_type(unpickled), six.text_type(x)) self.assertEqual(unpickled.name, x.name) + + def test_dict(self): + # See ticket #18447 + lazydict = SimpleLazyObject(lambda: {'one': 1}) + self.assertEqual(lazydict['one'], 1) + lazydict['one'] = -1 + self.assertEqual(lazydict['one'], -1) + del lazydict['one'] + with self.assertRaises(KeyError): + lazydict['one'] + + def test_trace(self): + # See ticket #19456 + old_trace_func = sys.gettrace() + try: + def trace_func(frame, event, arg): + frame.f_locals['self'].__class__ + if old_trace_func is not None: + old_trace_func(frame, event, arg) + sys.settrace(trace_func) + SimpleLazyObject(None) + finally: + sys.settrace(old_trace_func) diff --git a/tests/regressiontests/utils/termcolors.py b/tests/utils_tests/test_termcolors.py index 23fcf557f8..8cc28009cd 100644 --- a/tests/regressiontests/utils/termcolors.py +++ b/tests/utils_tests/test_termcolors.py @@ -1,5 +1,7 @@ from django.utils import unittest -from django.utils.termcolors import parse_color_setting, PALETTES, DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE +from django.utils.termcolors import (parse_color_setting, PALETTES, + DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE, colorize) + class TermColorTests(unittest.TestCase): @@ -146,3 +148,10 @@ class TermColorTests(unittest.TestCase): self.assertEqual(parse_color_setting('error=green,bLiNk'), dict(PALETTES[NOCOLOR_PALETTE], ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_colorize_empty_text(self): + self.assertEqual(colorize(text=None), '\x1b[m\x1b[0m') + self.assertEqual(colorize(text=''), '\x1b[m\x1b[0m') + + self.assertEqual(colorize(text=None, opts=('noreset')), '\x1b[m') + self.assertEqual(colorize(text='', opts=('noreset')), '\x1b[m') diff --git a/tests/regressiontests/utils/text.py b/tests/utils_tests/test_text.py index ebf67952f9..c9dde6b1b3 100644 --- a/tests/regressiontests/utils/text.py +++ b/tests/utils_tests/test_text.py @@ -8,8 +8,6 @@ from django.utils import text class TestUtilsText(SimpleTestCase): - # In Django 1.6 truncate_words() and truncate_html_words() will be removed - # so these tests will need to be adapted accordingly def test_truncate_chars(self): truncator = text.Truncator( 'The quick brown fox jumped over the lazy dog.' @@ -57,45 +55,32 @@ class TestUtilsText(SimpleTestCase): truncator.words(4, '[snip]')) def test_truncate_html_words(self): - truncator = text.Truncator('<p><strong><em>The quick brown fox jumped ' - 'over the lazy dog.</em></strong></p>') - self.assertEqual('<p><strong><em>The quick brown fox jumped over the ' - 'lazy dog.</em></strong></p>', truncator.words(10, html=True)) - self.assertEqual('<p><strong><em>The quick brown fox...</em>' + truncator = text.Truncator('<p id="par"><strong><em>The quick brown fox' + ' jumped over the lazy dog.</em></strong></p>') + self.assertEqual('<p id="par"><strong><em>The quick brown fox jumped over' + ' the lazy dog.</em></strong></p>', truncator.words(10, html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox...</em>' '</strong></p>', truncator.words(4, html=True)) - self.assertEqual('<p><strong><em>The quick brown fox....</em>' + self.assertEqual('<p id="par"><strong><em>The quick brown fox....</em>' '</strong></p>', truncator.words(4, '....', html=True)) - self.assertEqual('<p><strong><em>The quick brown fox</em></strong>' - '</p>', truncator.words(4, '', html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox</em>' + '</strong></p>', truncator.words(4, '', html=True)) + # Test with new line inside tag truncator = text.Truncator('<p>The quick <a href="xyz.html"\n' 'id="mylink">brown fox</a> jumped over the lazy dog.</p>') self.assertEqual('<p>The quick <a href="xyz.html"\n' 'id="mylink">brown...</a></p>', truncator.words(3, '...', html=True)) - def test_old_truncate_words(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.assertEqual('The quick brown fox jumped over the lazy dog.', - text.truncate_words('The quick brown fox jumped over the lazy dog.', 10)) - self.assertEqual('The quick brown fox ...', - text.truncate_words('The quick brown fox jumped over the lazy dog.', 4)) - self.assertEqual('The quick brown fox ....', - text.truncate_words('The quick brown fox jumped over the lazy dog.', 4, '....')) - self.assertGreater(len(w), 0) - - def test_old_truncate_html_words(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.assertEqual('<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('<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('<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('<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)) - self.assertGreater(len(w), 0) + # Test self-closing tags + truncator = text.Truncator('<br/>The <hr />quick brown fox jumped over' + ' the lazy dog.') + self.assertEqual('<br/>The <hr />quick brown...', + truncator.words(3, '...', html=True )) + truncator = text.Truncator('<br>The <hr/>quick <em>brown fox</em> ' + 'jumped over the lazy dog.') + self.assertEqual('<br>The <hr/>quick <em>brown...</em>', + truncator.words(3, '...', html=True )) def test_wrap(self): digits = '1234 67 9' diff --git a/tests/regressiontests/utils/timesince.py b/tests/utils_tests/test_timesince.py index 5e641a42c4..5e641a42c4 100644 --- a/tests/regressiontests/utils/timesince.py +++ b/tests/utils_tests/test_timesince.py diff --git a/tests/regressiontests/utils/timezone.py b/tests/utils_tests/test_timezone.py index 991600c8b9..650198e8ff 100644 --- a/tests/regressiontests/utils/timezone.py +++ b/tests/utils_tests/test_timezone.py @@ -3,9 +3,14 @@ import datetime import pickle from django.test.utils import override_settings from django.utils import timezone +from django.utils.tzinfo import FixedOffset from django.utils import unittest +EAT = FixedOffset(180) # Africa/Nairobi +ICT = FixedOffset(420) # Asia/Bangkok + + class TimezoneTests(unittest.TestCase): def test_localtime(self): @@ -20,6 +25,31 @@ class TimezoneTests(unittest.TestCase): with override_settings(USE_TZ=False): self.assertTrue(timezone.is_naive(timezone.now())) + def test_override(self): + default = timezone.get_default_timezone() + try: + timezone.activate(ICT) + + with timezone.override(EAT): + self.assertIs(EAT, timezone.get_current_timezone()) + self.assertIs(ICT, timezone.get_current_timezone()) + + with timezone.override(None): + self.assertIs(default, timezone.get_current_timezone()) + self.assertIs(ICT, timezone.get_current_timezone()) + + timezone.deactivate() + + with timezone.override(EAT): + self.assertIs(EAT, timezone.get_current_timezone()) + self.assertIs(default, timezone.get_current_timezone()) + + with timezone.override(None): + self.assertIs(default, timezone.get_current_timezone()) + self.assertIs(default, timezone.get_current_timezone()) + finally: + timezone.deactivate() + def test_copy(self): self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC) self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone) diff --git a/tests/regressiontests/utils/tzinfo.py b/tests/utils_tests/test_tzinfo.py index cec92652cd..cec92652cd 100644 --- a/tests/regressiontests/utils/tzinfo.py +++ b/tests/utils_tests/test_tzinfo.py diff --git a/tests/utils_tests/tests.py b/tests/utils_tests/tests.py new file mode 100644 index 0000000000..8d7fda4800 --- /dev/null +++ b/tests/utils_tests/tests.py @@ -0,0 +1,34 @@ +""" +Tests for django.utils. +""" +from __future__ import absolute_import + +from .test_archive import TestBzip2Tar, TestGzipTar, TestTar, TestZip +from .test_baseconv import TestBaseConv +from .test_checksums import TestUtilsChecksums +from .test_crypto import TestUtilsCryptoMisc, TestUtilsCryptoPBKDF2 +from .test_datastructures import (DictWrapperTests, ImmutableListTests, + MergeDictTests, MultiValueDictTests, SortedDictTests) +from .test_dateformat import DateFormatTests +from .test_dateparse import DateParseTests +from .test_datetime_safe import DatetimeTests +from .test_decorators import DecoratorFromMiddlewareTests +from .test_encoding import TestEncodingUtils +from .test_feedgenerator import FeedgeneratorTest +from .test_functional import FunctionalTestCase +from .test_html import TestUtilsHtml +from .test_http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests +from .test_itercompat import TestIsIterator +from .test_ipv6 import TestUtilsIPv6 +from .test_jslex import JsToCForGettextTest, JsTokensTest +from .test_module_loading import (CustomLoader, DefaultLoader, EggLoader, + ModuleImportTestCase) +from .test_numberformat import TestNumberFormat +from .test_os_utils import SafeJoinTests +from .test_regex_helper import NormalizeTests +from .test_simplelazyobject import TestUtilsSimpleLazyObject +from .test_termcolors import TermColorTests +from .test_text import TestUtilsText +from .test_timesince import TimesinceTests +from .test_timezone import TimezoneTests +from .test_tzinfo import TzinfoTests diff --git a/tests/modeltests/validation/__init__.py b/tests/validation/__init__.py index 5daa06be66..5daa06be66 100644 --- a/tests/modeltests/validation/__init__.py +++ b/tests/validation/__init__.py diff --git a/tests/modeltests/validation/models.py b/tests/validation/models.py index db083290fb..db083290fb 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/validation/models.py diff --git a/tests/modeltests/validation/test_custom_messages.py b/tests/validation/test_custom_messages.py index c5a1ee744f..c5a1ee744f 100644 --- a/tests/modeltests/validation/test_custom_messages.py +++ b/tests/validation/test_custom_messages.py diff --git a/tests/modeltests/validation/test_error_messages.py b/tests/validation/test_error_messages.py index 63c033a750..63c033a750 100644 --- a/tests/modeltests/validation/test_error_messages.py +++ b/tests/validation/test_error_messages.py diff --git a/tests/modeltests/validation/test_unique.py b/tests/validation/test_unique.py index 038da16494..038da16494 100644 --- a/tests/modeltests/validation/test_unique.py +++ b/tests/validation/test_unique.py diff --git a/tests/modeltests/validation/validators.py b/tests/validation/test_validators.py index e58d9fd4a6..e58d9fd4a6 100644 --- a/tests/modeltests/validation/validators.py +++ b/tests/validation/test_validators.py diff --git a/tests/modeltests/validation/tests.py b/tests/validation/tests.py index 58b7b94e7f..ead1b57981 100644 --- a/tests/modeltests/validation/tests.py +++ b/tests/validation/tests.py @@ -12,7 +12,7 @@ from .models import (Author, Article, ModelToValidate, from .test_custom_messages import CustomMessagesTest from .test_error_messages import ValidationMessagesTest from .test_unique import GetUniqueCheckTests, PerformUniqueChecksTest -from .validators import TestModelsWithValidators +from .test_validators import TestModelsWithValidators class BaseModelValidationTests(ValidationTestCase): @@ -55,7 +55,7 @@ class BaseModelValidationTests(ValidationTestCase): def test_wrong_url_value_raises_error(self): mtv = ModelToValidate(number=10, name='Some Name', url='not a url') - self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', ['Enter a valid value.']) + self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url', ['Enter a valid URL.']) def test_text_greater_that_charfields_max_length_raises_erros(self): mtv = ModelToValidate(number=10, name='Some Name'*100) diff --git a/tests/validators/__init__.py b/tests/validators/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/validators/__init__.py diff --git a/tests/validators/models.py b/tests/validators/models.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/validators/models.py diff --git a/tests/modeltests/validators/tests.py b/tests/validators/tests.py index 0174a606df..6b46c53cc3 100644 --- a/tests/modeltests/validators/tests.py +++ b/tests/validators/tests.py @@ -29,6 +29,9 @@ TEST_DATA = ( (validate_email, 'example@valid-----hyphens.com', None), (validate_email, 'example@valid-with-hyphens.com', None), (validate_email, 'test@domain.with.idn.tld.उदाहरण.परीक्षा', None), + (validate_email, 'email@localhost', None), + (EmailValidator(whitelist=['localdomain']), 'email@localdomain', None), + (validate_email, '"test@test"@example.com', None), (validate_email, None, ValidationError), (validate_email, '', ValidationError), diff --git a/tests/version/__init__.py b/tests/version/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/version/__init__.py diff --git a/tests/version/models.py b/tests/version/models.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/version/models.py diff --git a/tests/regressiontests/version/tests.py b/tests/version/tests.py index 9b849ee4ba..64621a5cb6 100644 --- a/tests/regressiontests/version/tests.py +++ b/tests/version/tests.py @@ -1,6 +1,7 @@ import re from django import get_version +from django.utils import six from django.utils.unittest import TestCase class VersionTests(TestCase): @@ -10,7 +11,7 @@ class VersionTests(TestCase): # This will return a different result when it's run within or outside # of a git clone: 1.4.devYYYYMMDDHHMMSS or 1.4. ver_string = get_version(ver_tuple) - self.assertRegexpMatches(ver_string, r'1\.4(\.dev\d+)?') + six.assertRegex(self, ver_string, r'1\.4(\.dev\d+)?') def test_releases(self): tuples_to_strings = ( diff --git a/tests/regressiontests/views/__init__.py b/tests/view_tests/__init__.py index 1193ffe010..1193ffe010 100644 --- a/tests/regressiontests/views/__init__.py +++ b/tests/view_tests/__init__.py diff --git a/tests/regressiontests/views/app0/__init__.py b/tests/view_tests/app0/__init__.py index 792d600548..792d600548 100644 --- a/tests/regressiontests/views/app0/__init__.py +++ b/tests/view_tests/app0/__init__.py diff --git a/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mo b/tests/view_tests/app0/locale/en/LC_MESSAGES/djangojs.mo Binary files differindex 662204a6d7..662204a6d7 100644 --- a/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/app0/locale/en/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po b/tests/view_tests/app0/locale/en/LC_MESSAGES/djangojs.po index a458935f96..a458935f96 100644 --- a/tests/regressiontests/views/app0/locale/en/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/app0/locale/en/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/app1/__init__.py b/tests/view_tests/app1/__init__.py index 792d600548..792d600548 100644 --- a/tests/regressiontests/views/app1/__init__.py +++ b/tests/view_tests/app1/__init__.py diff --git a/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo b/tests/view_tests/app1/locale/fr/LC_MESSAGES/djangojs.mo Binary files differindex 5d6aecb446..5d6aecb446 100644 --- a/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/app1/locale/fr/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po b/tests/view_tests/app1/locale/fr/LC_MESSAGES/djangojs.po index a4627db877..a4627db877 100644 --- a/tests/regressiontests/views/app1/locale/fr/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/app1/locale/fr/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/app2/__init__.py b/tests/view_tests/app2/__init__.py index 792d600548..792d600548 100644 --- a/tests/regressiontests/views/app2/__init__.py +++ b/tests/view_tests/app2/__init__.py diff --git a/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo b/tests/view_tests/app2/locale/fr/LC_MESSAGES/djangojs.mo Binary files differindex 17e1863672..17e1863672 100644 --- a/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/app2/locale/fr/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po b/tests/view_tests/app2/locale/fr/LC_MESSAGES/djangojs.po index 637b9e6bcd..637b9e6bcd 100644 --- a/tests/regressiontests/views/app2/locale/fr/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/app2/locale/fr/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/app3/__init__.py b/tests/view_tests/app3/__init__.py index 792d600548..792d600548 100644 --- a/tests/regressiontests/views/app3/__init__.py +++ b/tests/view_tests/app3/__init__.py diff --git a/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo b/tests/view_tests/app3/locale/es_AR/LC_MESSAGES/djangojs.mo Binary files differindex 0c485a9838..0c485a9838 100644 --- a/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/app3/locale/es_AR/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po b/tests/view_tests/app3/locale/es_AR/LC_MESSAGES/djangojs.po index 1e3be0bfea..1e3be0bfea 100644 --- a/tests/regressiontests/views/app3/locale/es_AR/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/app3/locale/es_AR/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/app4/__init__.py b/tests/view_tests/app4/__init__.py index 792d600548..792d600548 100644 --- a/tests/regressiontests/views/app4/__init__.py +++ b/tests/view_tests/app4/__init__.py diff --git a/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo b/tests/view_tests/app4/locale/es_AR/LC_MESSAGES/djangojs.mo Binary files differindex 581fbb0347..581fbb0347 100644 --- a/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/app4/locale/es_AR/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po b/tests/view_tests/app4/locale/es_AR/LC_MESSAGES/djangojs.po index 27403c0783..27403c0783 100644 --- a/tests/regressiontests/views/app4/locale/es_AR/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/app4/locale/es_AR/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/fixtures/testdata.json b/tests/view_tests/fixtures/testdata.json index ab68407ab6..8663328c9e 100644 --- a/tests/regressiontests/views/fixtures/testdata.json +++ b/tests/view_tests/fixtures/testdata.json @@ -19,44 +19,44 @@ }, { "pk": 1, - "model": "views.author", + "model": "view_tests.author", "fields": { "name": "Boris" } }, { "pk": 1, - "model": "views.article", + "model": "view_tests.article", "fields": { "author": 1, "title": "Old Article", "slug": "old_article", "date_created": "2001-01-01 21:22:23" } - }, + }, { - "pk": 2, - "model": "views.article", + "pk": 2, + "model": "view_tests.article", "fields": { - "author": 1, + "author": 1, "title": "Current Article", "slug": "current_article", "date_created": "2007-09-17 21:22:23" } - }, + }, { - "pk": 3, - "model": "views.article", + "pk": 3, + "model": "view_tests.article", "fields": { - "author": 1, + "author": 1, "title": "Future Article", "slug": "future_article", "date_created": "3000-01-01 21:22:23" } - }, + }, { "pk": 1, - "model": "views.urlarticle", + "model": "view_tests.urlarticle", "fields": { "author": 1, "title": "Old Article", diff --git a/tests/regressiontests/views/generic_urls.py b/tests/view_tests/generic_urls.py index 0f214d12d5..c3ac1fcafa 100644 --- a/tests/regressiontests/views/generic_urls.py +++ b/tests/view_tests/generic_urls.py @@ -32,8 +32,8 @@ urlpatterns = patterns('', (r'^accounts/logout/$', 'django.contrib.auth.views.logout'), # Special URLs for particular regression cases. - url('^中文/$', 'regressiontests.views.views.redirect'), - url('^中文/target/$', 'regressiontests.views.views.index_page'), + url('^中文/$', 'view_tests.views.redirect'), + url('^中文/target/$', 'view_tests.views.index_page'), ) # rediriects, both temporary and permanent, with non-ASCII targets @@ -44,10 +44,10 @@ urlpatterns += patterns('', url='/中文/target/', permanent=True)), ) -urlpatterns += patterns('regressiontests.views.views', +urlpatterns += patterns('view_tests.views', (r'^shortcuts/render_to_response/$', 'render_to_response_view'), (r'^shortcuts/render_to_response/request_context/$', 'render_to_response_view_with_request_context'), - (r'^shortcuts/render_to_response/mimetype/$', 'render_to_response_view_with_mimetype'), + (r'^shortcuts/render_to_response/content_type/$', 'render_to_response_view_with_content_type'), (r'^shortcuts/render/$', 'render_view'), (r'^shortcuts/render/base_context/$', 'render_view_with_base_context'), (r'^shortcuts/render/content_type/$', 'render_view_with_content_type'), diff --git a/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo Binary files differindex 34ba691029..34ba691029 100644 --- a/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.po index 88cc35e88f..88cc35e88f 100644 --- a/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/de/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/es/LC_MESSAGES/djangojs.mo Binary files differindex b6b088752a..b6b088752a 100644 --- a/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/locale/es/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/es/LC_MESSAGES/djangojs.po index 669af4bf19..669af4bf19 100644 --- a/tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/es/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.mo Binary files differindex 537135ec95..537135ec95 100644 --- a/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po index 9259aab91b..9259aab91b 100644 --- a/tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo Binary files differindex 21659a93d3..21659a93d3 100644 --- a/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo +++ b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.mo diff --git a/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po index 4ea193a880..4ea193a880 100644 --- a/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/ru/LC_MESSAGES/djangojs.po diff --git a/tests/regressiontests/views/media/file.txt b/tests/view_tests/media/file.txt index f1fc82c455..f1fc82c455 100644 --- a/tests/regressiontests/views/media/file.txt +++ b/tests/view_tests/media/file.txt diff --git a/tests/regressiontests/views/media/file.txt.gz b/tests/view_tests/media/file.txt.gz Binary files differindex 0ee7d18311..0ee7d18311 100644 --- a/tests/regressiontests/views/media/file.txt.gz +++ b/tests/view_tests/media/file.txt.gz diff --git a/tests/regressiontests/views/media/file.unknown b/tests/view_tests/media/file.unknown index 77dcda8970..77dcda8970 100644 --- a/tests/regressiontests/views/media/file.unknown +++ b/tests/view_tests/media/file.unknown diff --git a/tests/regressiontests/views/models.py b/tests/view_tests/models.py index 461f98c028..461f98c028 100644 --- a/tests/regressiontests/views/models.py +++ b/tests/view_tests/models.py diff --git a/tests/regressiontests/views/templates/debug/render_test.html b/tests/view_tests/templates/debug/render_test.html index 1b2f47a0e3..1b2f47a0e3 100644 --- a/tests/regressiontests/views/templates/debug/render_test.html +++ b/tests/view_tests/templates/debug/render_test.html diff --git a/tests/regressiontests/views/templates/debug/template_exception.html b/tests/view_tests/templates/debug/template_exception.html index c6b34a8388..c6b34a8388 100644 --- a/tests/regressiontests/views/templates/debug/template_exception.html +++ b/tests/view_tests/templates/debug/template_exception.html diff --git a/tests/regressiontests/views/templates/jsi18n.html b/tests/view_tests/templates/jsi18n.html index d6093c8ef4..d6093c8ef4 100644 --- a/tests/regressiontests/views/templates/jsi18n.html +++ b/tests/view_tests/templates/jsi18n.html diff --git a/tests/view_tests/templatetags/__init__.py b/tests/view_tests/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/view_tests/templatetags/__init__.py diff --git a/tests/regressiontests/views/templatetags/debugtags.py b/tests/view_tests/templatetags/debugtags.py index cd2d2d9ad2..cd2d2d9ad2 100644 --- a/tests/regressiontests/views/templatetags/debugtags.py +++ b/tests/view_tests/templatetags/debugtags.py diff --git a/tests/view_tests/tests/__init__.py b/tests/view_tests/tests/__init__.py new file mode 100644 index 0000000000..bfb2ed7376 --- /dev/null +++ b/tests/view_tests/tests/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import + +from .test_debug import (DebugViewTests, ExceptionReporterTests, + ExceptionReporterTests, PlainTextReportTests, ExceptionReporterFilterTests, + AjaxResponseExceptionReporterFilter) +from .test_defaults import DefaultsTests +from .test_i18n import JsI18NTests, I18NTests, JsI18NTestsMultiPackage, JavascriptI18nTests +from .test_shortcuts import ShortcutTests +from .test_specials import URLHandling +from .test_static import StaticHelperTest, StaticUtilsTests, StaticTests diff --git a/tests/regressiontests/views/tests/debug.py b/tests/view_tests/tests/test_debug.py index 4fdaad5010..b44cd88abe 100644 --- a/tests/regressiontests/views/tests/debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -7,7 +7,6 @@ import inspect import os import sys -from django.conf import settings from django.core import mail from django.core.files.uploadedfile import SimpleUploadedFile from django.core.urlresolvers import reverse @@ -19,12 +18,13 @@ from django.views.debug import ExceptionReporter from .. import BrokenException, except_args from ..views import (sensitive_view, non_sensitive_view, paranoid_view, - custom_exception_reporter_filter_view, sensitive_method_view) + custom_exception_reporter_filter_view, sensitive_method_view, + sensitive_args_function_caller, sensitive_kwargs_function_caller) @override_settings(DEBUG=True, TEMPLATE_DEBUG=True) class DebugViewTests(TestCase): - urls = "regressiontests.views.urls" + urls = "view_tests.urls" def test_files(self): response = self.client.get('/raises/') @@ -306,17 +306,28 @@ class ExceptionReportTestMixin(object): response = view(request) self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] + # Frames vars are never shown in plain text email reports. - body = force_text(email.body) - self.assertNotIn('cooked_eggs', body) - self.assertNotIn('scrambled', body) - self.assertNotIn('sauce', body) - self.assertNotIn('worcestershire', body) + body_plain = force_text(email.body) + self.assertNotIn('cooked_eggs', body_plain) + self.assertNotIn('scrambled', body_plain) + self.assertNotIn('sauce', body_plain) + self.assertNotIn('worcestershire', body_plain) + + # Frames vars are shown in html email reports. + body_html = force_text(email.alternatives[0][0]) + self.assertIn('cooked_eggs', body_html) + self.assertIn('scrambled', body_html) + self.assertIn('sauce', body_html) + self.assertIn('worcestershire', body_html) + if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters are shown. - self.assertIn(k, body) - self.assertIn(v, body) + self.assertIn(k, body_plain) + self.assertIn(v, body_plain) + self.assertIn(k, body_html) + self.assertIn(v, body_html) def verify_safe_email(self, view, check_for_POST_params=True): """ @@ -328,22 +339,35 @@ class ExceptionReportTestMixin(object): response = view(request) self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] + # Frames vars are never shown in plain text email reports. - body = force_text(email.body) - self.assertNotIn('cooked_eggs', body) - self.assertNotIn('scrambled', body) - self.assertNotIn('sauce', body) - self.assertNotIn('worcestershire', body) + body_plain = force_text(email.body) + self.assertNotIn('cooked_eggs', body_plain) + self.assertNotIn('scrambled', body_plain) + self.assertNotIn('sauce', body_plain) + self.assertNotIn('worcestershire', body_plain) + + # Frames vars are shown in html email reports. + body_html = force_text(email.alternatives[0][0]) + self.assertIn('cooked_eggs', body_html) + self.assertIn('scrambled', body_html) + self.assertIn('sauce', body_html) + self.assertNotIn('worcestershire', body_html) + if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. - self.assertIn(k, body) + self.assertIn(k, body_plain) # Non-sensitive POST parameters' values are shown. - self.assertIn('baked-beans-value', body) - self.assertIn('hash-brown-value', body) + self.assertIn('baked-beans-value', body_plain) + self.assertIn('hash-brown-value', body_plain) + self.assertIn('baked-beans-value', body_html) + self.assertIn('hash-brown-value', body_html) # Sensitive POST parameters' values are not shown. - self.assertNotIn('sausage-value', body) - self.assertNotIn('bacon-value', body) + self.assertNotIn('sausage-value', body_plain) + self.assertNotIn('bacon-value', body_plain) + self.assertNotIn('sausage-value', body_html) + self.assertNotIn('bacon-value', body_html) def verify_paranoid_email(self, view): """ @@ -445,6 +469,36 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): self.verify_safe_email(sensitive_method_view, check_for_POST_params=False) + def test_sensitive_function_arguments(self): + """ + Ensure that sensitive variables don't leak in the sensitive_variables + decorator's frame, when those variables are passed as arguments to the + decorated function. + Refs #19453. + """ + with self.settings(DEBUG=True): + self.verify_unsafe_response(sensitive_args_function_caller) + self.verify_unsafe_email(sensitive_args_function_caller) + + with self.settings(DEBUG=False): + self.verify_safe_response(sensitive_args_function_caller, check_for_POST_params=False) + self.verify_safe_email(sensitive_args_function_caller, check_for_POST_params=False) + + def test_sensitive_function_keyword_arguments(self): + """ + Ensure that sensitive variables don't leak in the sensitive_variables + decorator's frame, when those variables are passed as keyword arguments + to the decorated function. + Refs #19453. + """ + with self.settings(DEBUG=True): + self.verify_unsafe_response(sensitive_kwargs_function_caller) + self.verify_unsafe_email(sensitive_kwargs_function_caller) + + with self.settings(DEBUG=False): + self.verify_safe_response(sensitive_kwargs_function_caller, check_for_POST_params=False) + self.verify_safe_email(sensitive_kwargs_function_caller, check_for_POST_params=False) + class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): """ diff --git a/tests/regressiontests/views/tests/defaults.py b/tests/view_tests/tests/test_defaults.py index 3ca7f79136..5efd338d34 100644 --- a/tests/regressiontests/views/tests/defaults.py +++ b/tests/view_tests/tests/test_defaults.py @@ -13,43 +13,6 @@ class DefaultsTests(TestCase): non_existing_urls = ['/views/non_existing_url/', # this is in urls.py '/views/other_non_existing_url/'] # this NOT in urls.py - def test_shortcut_with_absolute_url(self): - "Can view a shortcut for an Author object that has a get_absolute_url method" - for obj in Author.objects.all(): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk) - response = self.client.get(short_url) - self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(), - status_code=302, target_status_code=404) - - def test_shortcut_no_absolute_url(self): - "Shortcuts for an object that has no get_absolute_url method raises 404" - for obj in Article.objects.all(): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk) - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_wrong_type_pk(self): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects') - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_shortcut_bad_pk(self): - short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242') - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_nonint_content_type(self): - an_author = Author.objects.all()[0] - short_url = '/views/shortcut/%s/%s/' % ('spam', an_author.pk) - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - - def test_bad_content_type(self): - an_author = Author.objects.all()[0] - short_url = '/views/shortcut/%s/%s/' % (42424242, an_author.pk) - response = self.client.get(short_url) - self.assertEqual(response.status_code, 404) - def test_page_not_found(self): "A 404 status is returned by the page_not_found view" for url in self.non_existing_urls: diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/view_tests/tests/test_i18n.py index b1dc8808a1..5a3bdd1062 100644 --- a/tests/regressiontests/views/tests/i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -2,6 +2,7 @@ from __future__ import absolute_import import gettext +import os from os import path from django.conf import settings @@ -44,7 +45,7 @@ class I18NTests(TestCase): lang_code, lang_name = settings.LANGUAGES[0] post_data = dict(language=lang_code, next='//unsafe/redirection/') response = self.client.post('/views/i18n/setlang/', data=post_data) - self.assertEqual(response['Location'], 'http://testserver/') + self.assertEqual(response.url, 'http://testserver/') self.assertEqual(self.client.session['django_language'], lang_code) def test_setlang_reversal(self): @@ -60,13 +61,13 @@ class I18NTests(TestCase): else: trans_txt = catalog.ugettext('this is to be translated') response = self.client.get('/views/jsi18n/') - # in response content must to be a line like that: - # catalog['this is to be translated'] = 'same_that_trans_txt' + # response content must include a line like: + # "this is to be translated": <value of trans_txt Python variable> # javascript_quote is used to be able to check unicode strings self.assertContains(response, javascript_quote(trans_txt), 1) if lang_code == 'fr': # Message with context (msgctxt) - self.assertContains(response, "['month name\x04May'] = 'mai';", 1) + self.assertContains(response, r'"month name\u0004May": "mai"', 1) class JsI18NTests(TestCase): @@ -118,7 +119,7 @@ class JsI18NTests(TestCase): available. The Javascript i18n view must return a NON empty language catalog with the proper English translations. See #13726 for more details. """ - extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views.app0'] + extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app0'] with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps): with override('en-us'): response = self.client.get('/views/jsi18n_english_translation/') @@ -148,7 +149,7 @@ class JsI18NTestsMultiPackage(TestCase): translations of multiple Python packages is requested. See #13388, #3594 and #13514 for more details. """ - extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views.app1', 'regressiontests.views.app2'] + extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app1', 'view_tests.app2'] with self.settings(LANGUAGE_CODE='en-us', INSTALLED_APPS=extended_apps): with override('fr'): response = self.client.get('/views/jsi18n_multi_packages1/') @@ -159,7 +160,7 @@ class JsI18NTestsMultiPackage(TestCase): Similar to above but with neither default or requested language being English. """ - extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views.app3', 'regressiontests.views.app4'] + extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app3', 'view_tests.app4'] with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps): with override('es-ar'): response = self.client.get('/views/jsi18n_multi_packages2/') @@ -176,9 +177,13 @@ class JsI18NTestsMultiPackage(TestCase): javascript_quote('este texto de app3 debe ser traducido')) +skip_selenium = not os.environ.get('DJANGO_SELENIUM_TESTS', False) + + +@unittest.skipIf(skip_selenium, 'Selenium tests not requested') @unittest.skipUnless(firefox, 'Selenium not installed') class JavascriptI18nTests(LiveServerTestCase): - urls = 'regressiontests.views.urls' + urls = 'view_tests.urls' @classmethod def setUpClass(cls): @@ -192,7 +197,7 @@ class JavascriptI18nTests(LiveServerTestCase): @override_settings(LANGUAGE_CODE='de') def test_javascript_gettext(self): - extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views'] + extended_apps = list(settings.INSTALLED_APPS) + ['view_tests'] with self.settings(INSTALLED_APPS=extended_apps): self.selenium.get('%s%s' % (self.live_server_url, '/jsi18n_template/')) @@ -210,7 +215,7 @@ class JavascriptI18nTests(LiveServerTestCase): self.assertEqual(elem.text, "455 Resultate") def test_escaping(self): - extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views'] + extended_apps = list(settings.INSTALLED_APPS) + ['view_tests'] with self.settings(INSTALLED_APPS=extended_apps): response = self.client.get('%s%s' % (self.live_server_url, '/jsi18n_admin/')) self.assertContains(response, '\\x04') diff --git a/tests/regressiontests/views/tests/shortcuts.py b/tests/view_tests/tests/test_shortcuts.py index 62bd82f4a7..74c556f41a 100644 --- a/tests/regressiontests/views/tests/shortcuts.py +++ b/tests/view_tests/tests/test_shortcuts.py @@ -7,7 +7,7 @@ from django.test.utils import override_settings STATIC_URL='/path/to/static/media/', ) class ShortcutTests(TestCase): - urls = 'regressiontests.views.generic_urls' + urls = 'view_tests.generic_urls' def test_render_to_response(self): response = self.client.get('/shortcuts/render_to_response/') @@ -21,8 +21,8 @@ class ShortcutTests(TestCase): self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n') self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') - def test_render_to_response_with_mimetype(self): - response = self.client.get('/shortcuts/render_to_response/mimetype/') + def test_render_to_response_with_content_type(self): + response = self.client.get('/shortcuts/render_to_response/content_type/') self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'FOO.BAR..\n') self.assertEqual(response['Content-Type'], 'application/x-rendertest') diff --git a/tests/regressiontests/views/tests/specials.py b/tests/view_tests/tests/test_specials.py index 7d652841f8..fc1dd98c0e 100644 --- a/tests/regressiontests/views/tests/specials.py +++ b/tests/view_tests/tests/test_specials.py @@ -8,7 +8,7 @@ class URLHandling(TestCase): """ Tests for URL handling in views and responses. """ - urls = 'regressiontests.views.generic_urls' + urls = 'view_tests.generic_urls' redirect_target = "/%E4%B8%AD%E6%96%87/target/" def test_combining_redirect(self): diff --git a/tests/regressiontests/views/tests/static.py b/tests/view_tests/tests/test_static.py index 8b8ef8ba9b..bdd9fbfc0b 100644 --- a/tests/regressiontests/views/tests/static.py +++ b/tests/view_tests/tests/test_static.py @@ -27,7 +27,6 @@ class StaticTests(TestCase): for filename in media_files: response = self.client.get('/views/%s/%s' % (self.prefix, filename)) response_content = b''.join(response) - response.close() file_path = path.join(media_dir, filename) with open(file_path, 'rb') as fp: self.assertEqual(fp.read(), response_content) @@ -36,14 +35,12 @@ class StaticTests(TestCase): def test_unknown_mime_type(self): response = self.client.get('/views/%s/file.unknown' % self.prefix) - response.close() self.assertEqual('application/octet-stream', response['Content-Type']) def test_copes_with_empty_path_component(self): file_name = 'file.txt' response = self.client.get('/views/%s//%s' % (self.prefix, file_name)) response_content = b''.join(response) - response.close() with open(path.join(media_dir, file_name), 'rb') as fp: self.assertEqual(fp.read(), response_content) @@ -52,7 +49,6 @@ class StaticTests(TestCase): response = self.client.get('/views/%s/%s' % (self.prefix, file_name), HTTP_IF_MODIFIED_SINCE='Thu, 1 Jan 1970 00:00:00 GMT') response_content = b''.join(response) - response.close() with open(path.join(media_dir, file_name), 'rb') as fp: self.assertEqual(fp.read(), response_content) @@ -77,7 +73,6 @@ class StaticTests(TestCase): response = self.client.get('/views/%s/%s' % (self.prefix, file_name), HTTP_IF_MODIFIED_SINCE=invalid_date) response_content = b''.join(response) - response.close() with open(path.join(media_dir, file_name), 'rb') as fp: self.assertEqual(fp.read(), response_content) self.assertEqual(len(response_content), @@ -94,7 +89,6 @@ class StaticTests(TestCase): response = self.client.get('/views/%s/%s' % (self.prefix, file_name), HTTP_IF_MODIFIED_SINCE=invalid_date) response_content = b''.join(response) - response.close() with open(path.join(media_dir, file_name), 'rb') as fp: self.assertEqual(fp.read(), response_content) self.assertEqual(len(response_content), diff --git a/tests/regressiontests/views/urls.py b/tests/view_tests/urls.py index 2c06557ae9..52e2eb474e 100644 --- a/tests/regressiontests/views/urls.py +++ b/tests/view_tests/urls.py @@ -15,34 +15,33 @@ locale_dir = path.join(base_dir, 'locale') js_info_dict = { 'domain': 'djangojs', - 'packages': ('regressiontests.views',), + 'packages': ('view_tests',), } js_info_dict_english_translation = { 'domain': 'djangojs', - 'packages': ('regressiontests.views.app0',), + 'packages': ('view_tests.app0',), } js_info_dict_multi_packages1 = { 'domain': 'djangojs', - 'packages': ('regressiontests.views.app1', 'regressiontests.views.app2'), + 'packages': ('view_tests.app1', 'view_tests.app2'), } js_info_dict_multi_packages2 = { 'domain': 'djangojs', - 'packages': ('regressiontests.views.app3', 'regressiontests.views.app4'), + 'packages': ('view_tests.app3', 'view_tests.app4'), } js_info_dict_admin = { 'domain': 'djangojs', - 'packages': ('django.contrib.admin', 'regressiontests.views'), + 'packages': ('django.contrib.admin', 'view_tests'), } urlpatterns = patterns('', (r'^$', views.index_page), # Default views - (r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'), (r'^non_existing_url/', 'django.views.defaults.page_not_found'), (r'^server_error/', 'django.views.defaults.server_error'), @@ -64,7 +63,7 @@ urlpatterns = patterns('', (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}), ) -urlpatterns += patterns('regressiontests.views.views', +urlpatterns += patterns('view_tests.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'), url(r'^raises_template_does_not_exist/$', 'raises_template_does_not_exist', name='raises_template_does_not_exist'), diff --git a/tests/regressiontests/views/views.py b/tests/view_tests/views.py index ed9d61144a..50ad98ac2d 100644 --- a/tests/regressiontests/views/views.py +++ b/tests/view_tests/views.py @@ -68,11 +68,11 @@ def render_to_response_view_with_request_context(request): 'bar': 'BAR', }, context_instance=RequestContext(request)) -def render_to_response_view_with_mimetype(request): +def render_to_response_view_with_content_type(request): return render_to_response('debug/render_test.html', { 'foo': 'FOO', 'bar': 'BAR', - }, mimetype='application/x-rendertest') + }, content_type='application/x-rendertest') def render_view(request): return render(request, 'debug/render_test.html', { @@ -132,6 +132,7 @@ def send_log(request, exc_info): ][0] orig_filters = admin_email_handler.filters admin_email_handler.filters = [] + admin_email_handler.include_html = True logger.error('Internal Server Error: %s', request.path, exc_info=exc_info, extra={ @@ -184,6 +185,38 @@ def paranoid_view(request): send_log(request, exc_info) return technical_500_response(request, *exc_info) +def sensitive_args_function_caller(request): + try: + sensitive_args_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])) + except Exception: + exc_info = sys.exc_info() + send_log(request, exc_info) + return technical_500_response(request, *exc_info) + +@sensitive_variables('sauce') +def sensitive_args_function(sauce): + # Do not just use plain strings for the variables' values in the code + # so that the tests don't return false positives when the function's source + # is displayed in the exception report. + cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) + raise Exception + +def sensitive_kwargs_function_caller(request): + try: + sensitive_kwargs_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])) + except Exception: + exc_info = sys.exc_info() + send_log(request, exc_info) + return technical_500_response(request, *exc_info) + +@sensitive_variables('sauce') +def sensitive_kwargs_function(sauce=None): + # Do not just use plain strings for the variables' values in the code + # so that the tests don't return false positives when the function's source + # is displayed in the exception report. + cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) + raise Exception + class UnsafeExceptionReporterFilter(SafeExceptionReporterFilter): """ Ignores all the filtering done by its parent class. @@ -230,4 +263,4 @@ class Klass(object): return technical_500_response(request, *exc_info) def sensitive_method_view(request): - return Klass().method(request)
\ No newline at end of file + return Klass().method(request) diff --git a/tests/wsgi/__init__.py b/tests/wsgi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/wsgi/__init__.py diff --git a/tests/wsgi/models.py b/tests/wsgi/models.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/wsgi/models.py diff --git a/tests/regressiontests/wsgi/tests.py b/tests/wsgi/tests.py index 6c1483d2cd..a66258d4eb 100644 --- a/tests/regressiontests/wsgi/tests.py +++ b/tests/wsgi/tests.py @@ -2,7 +2,9 @@ from __future__ import unicode_literals from django.core.exceptions import ImproperlyConfigured from django.core.servers.basehttp import get_internal_wsgi_application +from django.core.signals import request_started from django.core.wsgi import get_wsgi_application +from django.db import close_old_connections from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings @@ -10,7 +12,13 @@ from django.utils import six, unittest class WSGITest(TestCase): - urls = "regressiontests.wsgi.urls" + urls = "wsgi.urls" + + def setUp(self): + request_started.disconnect(close_old_connections) + + def tearDown(self): + request_started.connect(close_old_connections) def test_get_wsgi_application(self): """ @@ -44,7 +52,7 @@ class WSGITest(TestCase): class GetInternalWSGIApplicationTest(unittest.TestCase): - @override_settings(WSGI_APPLICATION="regressiontests.wsgi.wsgi.application") + @override_settings(WSGI_APPLICATION="wsgi.wsgi.application") def test_success(self): """ If ``WSGI_APPLICATION`` is a dotted path, the referenced object is @@ -81,19 +89,19 @@ class GetInternalWSGIApplicationTest(unittest.TestCase): basehttp.get_wsgi_application = _orig_get_wsgi_app - @override_settings(WSGI_APPLICATION="regressiontests.wsgi.noexist.app") + @override_settings(WSGI_APPLICATION="wsgi.noexist.app") def test_bad_module(self): with six.assertRaisesRegex(self, ImproperlyConfigured, - r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; could not import module 'regressiontests.wsgi.noexist':"): + r"^WSGI application 'wsgi.noexist.app' could not be loaded; Error importing.*"): get_internal_wsgi_application() - @override_settings(WSGI_APPLICATION="regressiontests.wsgi.wsgi.noexist") + @override_settings(WSGI_APPLICATION="wsgi.wsgi.noexist") def test_bad_name(self): with six.assertRaisesRegex(self, ImproperlyConfigured, - r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; can't find 'noexist' in module 'regressiontests.wsgi.wsgi':"): + r"^WSGI application 'wsgi.wsgi.noexist' could not be loaded; Module.*"): get_internal_wsgi_application() diff --git a/tests/regressiontests/wsgi/urls.py b/tests/wsgi/urls.py index 9639f0fd34..9639f0fd34 100644 --- a/tests/regressiontests/wsgi/urls.py +++ b/tests/wsgi/urls.py diff --git a/tests/regressiontests/wsgi/wsgi.py b/tests/wsgi/wsgi.py index 400457f152..400457f152 100644 --- a/tests/regressiontests/wsgi/wsgi.py +++ b/tests/wsgi/wsgi.py |
