summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2012-09-05 09:39:03 -0400
committerAndrew Godwin <andrew@aeracode.org>2012-09-05 09:39:03 -0400
commitb546e7eb633022ee1962570387f22fb2bcea46ed (patch)
treef87f4a2d68fb66afae39148fa35489930710b623
parentcd583d6dbd222ae61331a6965b0e1fc86c974c50 (diff)
parentcff911f4ba3b3e6393c58da5131ce8b188a68f0c (diff)
Merge branch 'master' into schema-alteration
-rw-r--r--AUTHORS2
-rw-r--r--MANIFEST.in2
-rwxr-xr-xdjango/bin/django-2to3.py10
-rw-r--r--django/conf/__init__.py14
-rw-r--r--django/conf/project_template/project_name/settings.py9
-rw-r--r--django/contrib/admin/models.py4
-rw-r--r--django/contrib/admin/templates/admin/change_form.html2
-rw-r--r--django/contrib/admin/templatetags/log.py2
-rw-r--r--django/contrib/admin/util.py4
-rw-r--r--django/contrib/admin/views/main.py6
-rw-r--r--django/contrib/admindocs/utils.py4
-rw-r--r--django/contrib/auth/decorators.py5
-rw-r--r--django/contrib/auth/forms.py10
-rw-r--r--django/contrib/auth/hashers.py10
-rw-r--r--django/contrib/auth/management/__init__.py18
-rw-r--r--django/contrib/auth/models.py16
-rw-r--r--django/contrib/auth/tests/basic.py44
-rw-r--r--django/contrib/auth/tests/management.py8
-rw-r--r--django/contrib/auth/tests/tokens.py3
-rw-r--r--django/contrib/auth/views.py9
-rw-r--r--django/contrib/comments/models.py7
-rw-r--r--django/contrib/contenttypes/models.py4
-rw-r--r--django/contrib/contenttypes/tests.py4
-rw-r--r--django/contrib/databrowse/datastructures.py19
-rw-r--r--django/contrib/databrowse/tests.py10
-rw-r--r--django/contrib/flatpages/models.py4
-rw-r--r--django/contrib/formtools/tests/__init__.py61
-rw-r--r--django/contrib/formtools/tests/forms.py1
-rw-r--r--django/contrib/formtools/tests/wizard/namedwizardtests/tests.py17
-rw-r--r--django/contrib/formtools/tests/wizard/wizardtests/tests.py15
-rw-r--r--django/contrib/formtools/utils.py2
-rw-r--r--django/contrib/formtools/wizard/storage/base.py4
-rw-r--r--django/contrib/gis/db/backends/base.py4
-rw-r--r--django/contrib/gis/db/backends/oracle/models.py4
-rw-r--r--django/contrib/gis/db/backends/postgis/models.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/creation.py1
-rw-r--r--django/contrib/gis/db/backends/spatialite/models.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py11
-rw-r--r--django/contrib/gis/db/models/query.py3
-rw-r--r--django/contrib/gis/gdal/tests/__init__.py3
-rw-r--r--django/contrib/gis/maps/google/overlays.py7
-rw-r--r--django/contrib/gis/sitemaps/views.py3
-rw-r--r--django/contrib/gis/tests/distapp/models.py22
-rw-r--r--django/contrib/gis/tests/geo3d/models.py26
-rw-r--r--django/contrib/gis/tests/geoadmin/models.py4
-rw-r--r--django/contrib/gis/tests/geoapp/models.py16
-rw-r--r--django/contrib/gis/tests/geoapp/tests.py9
-rw-r--r--django/contrib/gis/tests/geogapp/models.py10
-rw-r--r--django/contrib/gis/tests/relatedapp/models.py14
-rw-r--r--django/contrib/gis/utils/ogrinspect.py2
-rw-r--r--django/contrib/markup/templatetags/markup.py6
-rw-r--r--django/contrib/messages/storage/base.py7
-rw-r--r--django/contrib/redirects/models.py6
-rw-r--r--django/contrib/sessions/backends/base.py11
-rw-r--r--django/contrib/sessions/backends/db.py3
-rw-r--r--django/contrib/sessions/backends/file.py2
-rw-r--r--django/contrib/sitemaps/tests/generic.py7
-rw-r--r--django/contrib/sitemaps/tests/http.py36
-rw-r--r--django/contrib/sitemaps/tests/https.py22
-rw-r--r--django/contrib/sites/models.py7
-rw-r--r--django/contrib/staticfiles/management/commands/collectstatic.py2
-rw-r--r--django/contrib/staticfiles/storage.py20
-rw-r--r--django/contrib/syndication/views.py2
-rw-r--r--django/core/cache/backends/base.py6
-rw-r--r--django/core/cache/backends/db.py7
-rw-r--r--django/core/cache/backends/filebased.py3
-rw-r--r--django/core/cache/backends/memcached.py5
-rw-r--r--django/core/context_processors.py11
-rw-r--r--django/core/files/base.py18
-rw-r--r--django/core/files/move.py2
-rw-r--r--django/core/files/storage.py11
-rw-r--r--django/core/files/uploadedfile.py4
-rw-r--r--django/core/files/uploadhandler.py4
-rw-r--r--django/core/handlers/base.py25
-rw-r--r--django/core/handlers/wsgi.py4
-rw-r--r--django/core/mail/message.py7
-rw-r--r--django/core/management/__init__.py7
-rw-r--r--django/core/management/base.py4
-rw-r--r--django/core/management/commands/inspectdb.py116
-rw-r--r--django/core/management/commands/loaddata.py12
-rw-r--r--django/core/management/commands/makemessages.py60
-rw-r--r--django/core/management/commands/shell.py18
-rw-r--r--django/core/management/commands/syncdb.py2
-rw-r--r--django/core/management/sql.py5
-rw-r--r--django/core/management/templates.py6
-rw-r--r--django/core/management/validation.py4
-rw-r--r--django/core/serializers/base.py6
-rw-r--r--django/core/serializers/json.py8
-rw-r--r--django/core/serializers/pyyaml.py1
-rw-r--r--django/core/servers/basehttp.py10
-rw-r--r--django/core/signing.py47
-rw-r--r--django/core/urlresolvers.py40
-rw-r--r--django/core/validators.py15
-rw-r--r--django/db/backends/__init__.py28
-rw-r--r--django/db/backends/mysql/base.py8
-rw-r--r--django/db/backends/mysql/compiler.py10
-rw-r--r--django/db/backends/mysql/introspection.py22
-rw-r--r--django/db/backends/oracle/base.py23
-rw-r--r--django/db/backends/oracle/compiler.py9
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py6
-rw-r--r--django/db/backends/postgresql_psycopg2/introspection.py4
-rw-r--r--django/db/backends/sqlite3/base.py26
-rw-r--r--django/db/backends/sqlite3/introspection.py18
-rw-r--r--django/db/backends/util.py3
-rw-r--r--django/db/models/base.py30
-rw-r--r--django/db/models/expressions.py6
-rw-r--r--django/db/models/fields/__init__.py5
-rw-r--r--django/db/models/fields/files.py4
-rw-r--r--django/db/models/fields/subclassing.py5
-rw-r--r--django/db/models/options.py10
-rw-r--r--django/db/models/query.py8
-rw-r--r--django/db/models/sql/compiler.py14
-rw-r--r--django/db/models/sql/constants.py2
-rw-r--r--django/db/models/sql/query.py100
-rw-r--r--django/dispatch/saferef.py6
-rw-r--r--django/forms/forms.py12
-rw-r--r--django/forms/formsets.py29
-rw-r--r--django/forms/models.py16
-rw-r--r--django/forms/util.py12
-rw-r--r--django/forms/widgets.py24
-rw-r--r--django/http/__init__.py197
-rw-r--r--django/http/multipartparser.py12
-rw-r--r--django/middleware/csrf.py7
-rw-r--r--django/template/base.py11
-rw-r--r--django/template/defaultfilters.py19
-rw-r--r--django/template/defaulttags.py4
-rw-r--r--django/template/loaders/eggs.py13
-rw-r--r--django/template/response.py14
-rw-r--r--django/templatetags/cache.py6
-rw-r--r--django/test/client.py50
-rw-r--r--django/test/html.py7
-rw-r--r--django/test/signals.py9
-rw-r--r--django/test/testcases.py53
-rw-r--r--django/test/utils.py2
-rw-r--r--django/utils/2to3_fixes/__init__.py0
-rw-r--r--django/utils/2to3_fixes/fix_unicode.py36
-rw-r--r--django/utils/archive.py18
-rw-r--r--django/utils/autoreload.py5
-rw-r--r--django/utils/cache.py11
-rw-r--r--django/utils/crypto.py17
-rw-r--r--django/utils/dateparse.py18
-rw-r--r--django/utils/encoding.py49
-rw-r--r--django/utils/formats.py10
-rw-r--r--django/utils/functional.py1
-rw-r--r--django/utils/html.py19
-rw-r--r--django/utils/html_parser.py188
-rw-r--r--django/utils/http.py18
-rw-r--r--django/utils/ipv6.py2
-rw-r--r--django/utils/safestring.py74
-rw-r--r--django/utils/six.py52
-rw-r--r--django/utils/text.py21
-rw-r--r--django/utils/translation/__init__.py6
-rw-r--r--django/utils/translation/trans_real.py19
-rw-r--r--django/utils/tzinfo.py6
-rw-r--r--django/views/debug.py4
-rw-r--r--django/views/generic/base.py8
-rw-r--r--django/views/generic/dates.py17
-rw-r--r--django/views/static.py2
-rw-r--r--docs/conf.py12
-rw-r--r--docs/faq/general.txt2
-rw-r--r--docs/howto/custom-template-tags.txt6
-rw-r--r--docs/howto/deployment/wsgi/gunicorn.txt2
-rw-r--r--docs/index.txt9
-rw-r--r--docs/internals/committers.txt4
-rw-r--r--docs/internals/contributing/bugs-and-features.txt2
-rw-r--r--docs/intro/tutorial03.txt11
-rw-r--r--docs/ref/class-based-views/base.txt35
-rw-r--r--docs/ref/class-based-views/flattened-index.txt556
-rw-r--r--docs/ref/class-based-views/generic-date-based.txt27
-rw-r--r--docs/ref/class-based-views/generic-display.txt10
-rw-r--r--docs/ref/class-based-views/generic-editing.txt27
-rw-r--r--docs/ref/class-based-views/index.txt3
-rw-r--r--docs/ref/class-based-views/mixins-date-based.txt18
-rw-r--r--docs/ref/class-based-views/mixins-editing.txt14
-rw-r--r--docs/ref/class-based-views/mixins-multiple-object.txt6
-rw-r--r--docs/ref/class-based-views/mixins-simple.txt22
-rw-r--r--docs/ref/class-based-views/mixins-single-object.txt6
-rw-r--r--docs/ref/contrib/admin/index.txt8
-rw-r--r--docs/ref/contrib/comments/index.txt4
-rw-r--r--docs/ref/contrib/csrf.txt147
-rw-r--r--docs/ref/contrib/flatpages.txt43
-rw-r--r--docs/ref/contrib/formtools/form-wizard.txt6
-rw-r--r--docs/ref/contrib/gis/install.txt42
-rw-r--r--docs/ref/contrib/gis/testing.txt5
-rw-r--r--docs/ref/contrib/gis/tutorial.txt11
-rw-r--r--docs/ref/databases.txt8
-rw-r--r--docs/ref/files/file.txt12
-rw-r--r--docs/ref/models/fields.txt92
-rw-r--r--docs/ref/models/instances.txt12
-rw-r--r--docs/ref/models/querysets.txt16
-rw-r--r--docs/ref/request-response.txt19
-rw-r--r--docs/ref/settings.txt41
-rw-r--r--docs/ref/templates/api.txt10
-rw-r--r--docs/ref/utils.txt92
-rw-r--r--docs/releases/1.3.2.txt14
-rw-r--r--docs/releases/1.4.1.txt14
-rw-r--r--docs/releases/1.4.2.txt14
-rw-r--r--docs/releases/1.5.txt93
-rw-r--r--docs/releases/index.txt3
-rw-r--r--docs/topics/auth.txt21
-rw-r--r--docs/topics/cache.txt2
-rw-r--r--docs/topics/class-based-views/index.txt149
-rw-r--r--docs/topics/class-based-views/mixins.txt150
-rw-r--r--docs/topics/db/models.txt28
-rw-r--r--docs/topics/db/multi-db.txt2
-rw-r--r--docs/topics/db/queries.txt4
-rw-r--r--docs/topics/forms/media.txt60
-rw-r--r--docs/topics/forms/modelforms.txt4
-rw-r--r--docs/topics/http/file-uploads.txt46
-rw-r--r--docs/topics/http/urls.txt12
-rw-r--r--docs/topics/http/views.txt4
-rw-r--r--docs/topics/i18n/translation.txt22
-rw-r--r--docs/topics/logging.txt38
-rw-r--r--docs/topics/python3.txt308
-rw-r--r--docs/topics/templates.txt6
-rw-r--r--docs/topics/testing.txt2
-rw-r--r--setup.cfg2
-rw-r--r--tests/modeltests/aggregation/models.py13
-rw-r--r--tests/modeltests/basic/models.py4
-rw-r--r--tests/modeltests/choices/models.py4
-rw-r--r--tests/modeltests/custom_columns/models.py7
-rw-r--r--tests/modeltests/custom_managers/models.py10
-rw-r--r--tests/modeltests/custom_methods/models.py4
-rw-r--r--tests/modeltests/custom_pk/fields.py9
-rw-r--r--tests/modeltests/custom_pk/models.py10
-rw-r--r--tests/modeltests/defer/models.py4
-rw-r--r--tests/modeltests/delete/models.py4
-rw-r--r--tests/modeltests/distinct_on_fields/models.py13
-rw-r--r--tests/modeltests/expressions/models.py7
-rw-r--r--tests/modeltests/field_defaults/models.py4
-rw-r--r--tests/modeltests/field_subclassing/fields.py13
-rw-r--r--tests/modeltests/field_subclassing/models.py4
-rw-r--r--tests/modeltests/files/tests.py14
-rw-r--r--tests/modeltests/fixtures/models.py22
-rw-r--r--tests/modeltests/fixtures_model_package/models/__init__.py4
-rw-r--r--tests/modeltests/generic_relations/models.py16
-rw-r--r--tests/modeltests/get_latest/models.py7
-rw-r--r--tests/modeltests/get_object_or_404/models.py7
-rw-r--r--tests/modeltests/get_or_create/models.py4
-rw-r--r--tests/modeltests/invalid_models/tests.py4
-rw-r--r--tests/modeltests/lookup/models.py13
-rw-r--r--tests/modeltests/m2m_and_m2o/models.py4
-rw-r--r--tests/modeltests/m2m_intermediary/models.py10
-rw-r--r--tests/modeltests/m2m_multiple/models.py7
-rw-r--r--tests/modeltests/m2m_recursive/models.py4
-rw-r--r--tests/modeltests/m2m_signals/models.py10
-rw-r--r--tests/modeltests/m2m_through/models.py16
-rw-r--r--tests/modeltests/m2o_recursive/models.py7
-rw-r--r--tests/modeltests/many_to_many/models.py7
-rw-r--r--tests/modeltests/many_to_one/models.py7
-rw-r--r--tests/modeltests/many_to_one_null/models.py7
-rw-r--r--tests/modeltests/model_forms/models.py52
-rw-r--r--tests/modeltests/model_forms/tests.py69
-rw-r--r--tests/modeltests/model_formsets/models.py55
-rw-r--r--tests/modeltests/model_inheritance/models.py28
-rw-r--r--tests/modeltests/model_inheritance_same_model_name/models.py4
-rw-r--r--tests/modeltests/one_to_one/models.py13
-rw-r--r--tests/modeltests/or_lookups/models.py4
-rw-r--r--tests/modeltests/order_with_respect_to/models.py7
-rw-r--r--tests/modeltests/ordering/models.py7
-rw-r--r--tests/modeltests/pagination/models.py4
-rw-r--r--tests/modeltests/pagination/tests.py12
-rw-r--r--tests/modeltests/prefetch_related/models.py22
-rw-r--r--tests/modeltests/prefetch_related/tests.py2
-rw-r--r--tests/modeltests/proxy_model_inheritance/tests.py3
-rw-r--r--tests/modeltests/proxy_models/models.py13
-rw-r--r--tests/modeltests/reserved_names/models.py6
-rw-r--r--tests/modeltests/reverse_lookup/models.py10
-rw-r--r--tests/modeltests/save_delete_hooks/models.py4
-rw-r--r--tests/modeltests/select_related/models.py27
-rw-r--r--tests/modeltests/serializers/models.py29
-rw-r--r--tests/modeltests/serializers/tests.py18
-rw-r--r--tests/modeltests/signals/models.py7
-rw-r--r--tests/modeltests/str/models.py6
-rw-r--r--tests/modeltests/str/tests.py17
-rw-r--r--tests/modeltests/test_client/models.py2
-rw-r--r--tests/modeltests/timezones/tests.py1
-rw-r--r--tests/modeltests/transactions/models.py4
-rw-r--r--tests/modeltests/unmanaged_models/models.py19
-rw-r--r--tests/modeltests/update/models.py7
-rw-r--r--tests/modeltests/update_only_fields/models.py8
-rw-r--r--tests/modeltests/update_only_fields/tests.py101
-rw-r--r--tests/modeltests/validation/models.py6
-rw-r--r--tests/modeltests/validation/tests.py6
-rw-r--r--tests/regressiontests/admin_changelist/models.py7
-rw-r--r--tests/regressiontests/admin_changelist/tests.py4
-rw-r--r--tests/regressiontests/admin_custom_urls/fixtures/actions.json9
-rw-r--r--tests/regressiontests/admin_custom_urls/models.py6
-rw-r--r--tests/regressiontests/admin_custom_urls/tests.py43
-rw-r--r--tests/regressiontests/admin_filters/models.py10
-rw-r--r--tests/regressiontests/admin_inlines/models.py10
-rw-r--r--tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt2
-rw-r--r--tests/regressiontests/admin_scripts/models.py4
-rw-r--r--tests/regressiontests/admin_scripts/tests.py30
-rw-r--r--tests/regressiontests/admin_util/models.py4
-rw-r--r--tests/regressiontests/admin_util/tests.py2
-rw-r--r--tests/regressiontests/admin_validation/models.py4
-rw-r--r--tests/regressiontests/admin_views/models.py91
-rw-r--r--tests/regressiontests/admin_views/tests.py349
-rw-r--r--tests/regressiontests/admin_widgets/models.py24
-rw-r--r--tests/regressiontests/aggregation_regress/models.py16
-rw-r--r--tests/regressiontests/aggregation_regress/tests.py12
-rw-r--r--tests/regressiontests/backends/models.py13
-rw-r--r--tests/regressiontests/backends/tests.py8
-rw-r--r--tests/regressiontests/builtin_server/tests.py24
-rw-r--r--tests/regressiontests/cache/tests.py101
-rw-r--r--tests/regressiontests/comment_tests/models.py6
-rw-r--r--tests/regressiontests/conditional_processing/models.py6
-rw-r--r--tests/regressiontests/csrf_tests/tests.py2
-rw-r--r--tests/regressiontests/custom_columns_regress/models.py7
-rw-r--r--tests/regressiontests/custom_managers_regress/models.py10
-rw-r--r--tests/regressiontests/datatypes/models.py2
-rw-r--r--tests/regressiontests/dates/models.py7
-rw-r--r--tests/regressiontests/decorators/tests.py2
-rw-r--r--tests/regressiontests/defaultfilters/tests.py4
-rw-r--r--tests/regressiontests/defer_regress/models.py10
-rw-r--r--tests/regressiontests/dispatch/tests/test_saferef.py2
-rw-r--r--tests/regressiontests/expressions_regress/models.py4
-rw-r--r--tests/regressiontests/extra_regress/models.py7
-rw-r--r--tests/regressiontests/file_storage/tests.py81
-rw-r--r--tests/regressiontests/file_uploads/tests.py29
-rw-r--r--tests/regressiontests/file_uploads/views.py5
-rw-r--r--tests/regressiontests/fixtures_regress/models.py25
-rw-r--r--tests/regressiontests/fixtures_regress/tests.py27
-rw-r--r--tests/regressiontests/forms/models.py7
-rw-r--r--tests/regressiontests/forms/tests/error_messages.py4
-rw-r--r--tests/regressiontests/forms/tests/extra.py28
-rw-r--r--tests/regressiontests/forms/tests/fields.py12
-rw-r--r--tests/regressiontests/forms/tests/models.py4
-rw-r--r--tests/regressiontests/forms/tests/util.py4
-rw-r--r--tests/regressiontests/forms/tests/widgets.py7
-rw-r--r--tests/regressiontests/generic_inline_admin/models.py4
-rw-r--r--tests/regressiontests/generic_inline_admin/tests.py6
-rw-r--r--tests/regressiontests/generic_relations_regress/models.py16
-rw-r--r--tests/regressiontests/generic_views/base.py17
-rw-r--r--tests/regressiontests/generic_views/dates.py24
-rw-r--r--tests/regressiontests/generic_views/detail.py2
-rw-r--r--tests/regressiontests/generic_views/edit.py5
-rw-r--r--tests/regressiontests/generic_views/list.py2
-rw-r--r--tests/regressiontests/generic_views/models.py10
-rw-r--r--tests/regressiontests/generic_views/urls.py2
-rw-r--r--tests/regressiontests/httpwrappers/tests.py178
-rw-r--r--tests/regressiontests/i18n/tests.py18
-rw-r--r--tests/regressiontests/inline_formsets/models.py7
-rw-r--r--tests/regressiontests/inspectdb/models.py9
-rw-r--r--tests/regressiontests/inspectdb/tests.py40
-rw-r--r--tests/regressiontests/introspection/models.py7
-rw-r--r--tests/regressiontests/introspection/tests.py9
-rw-r--r--tests/regressiontests/m2m_regress/models.py13
-rw-r--r--tests/regressiontests/m2m_through_regress/models.py22
-rw-r--r--tests/regressiontests/m2m_through_regress/tests.py9
-rw-r--r--tests/regressiontests/mail/tests.py2
-rw-r--r--tests/regressiontests/managers_regress/models.py19
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py7
-rw-r--r--tests/regressiontests/middleware/tests.py14
-rw-r--r--tests/regressiontests/model_fields/models.py27
-rw-r--r--tests/regressiontests/model_fields/tests.py10
-rw-r--r--tests/regressiontests/model_forms_regress/models.py7
-rw-r--r--tests/regressiontests/model_forms_regress/tests.py5
-rw-r--r--tests/regressiontests/model_formsets_regress/models.py4
-rw-r--r--tests/regressiontests/model_inheritance_regress/models.py28
-rw-r--r--tests/regressiontests/model_inheritance_select_related/models.py10
-rw-r--r--tests/regressiontests/model_regress/models.py18
-rw-r--r--tests/regressiontests/modeladmin/models.py4
-rw-r--r--tests/regressiontests/modeladmin/tests.py58
-rw-r--r--tests/regressiontests/multiple_database/models.py13
-rw-r--r--tests/regressiontests/multiple_database/tests.py15
-rw-r--r--tests/regressiontests/nested_foreign_keys/__init__.py0
-rw-r--r--tests/regressiontests/nested_foreign_keys/models.py28
-rw-r--r--tests/regressiontests/nested_foreign_keys/tests.py166
-rw-r--r--tests/regressiontests/null_fk/models.py7
-rw-r--r--tests/regressiontests/null_fk_ordering/models.py10
-rw-r--r--tests/regressiontests/null_queries/models.py7
-rw-r--r--tests/regressiontests/one_to_one_regress/models.py13
-rw-r--r--tests/regressiontests/pagination_regress/tests.py6
-rw-r--r--tests/regressiontests/queries/models.py94
-rw-r--r--tests/regressiontests/queries/tests.py69
-rw-r--r--tests/regressiontests/requests/tests.py56
-rw-r--r--tests/regressiontests/select_related_onetoone/models.py16
-rw-r--r--tests/regressiontests/select_related_regress/models.py19
-rw-r--r--tests/regressiontests/serializers_regress/tests.py8
-rw-r--r--tests/regressiontests/servers/tests.py2
-rw-r--r--tests/regressiontests/settings_tests/tests.py10
-rw-r--r--tests/regressiontests/signals_regress/models.py7
-rw-r--r--tests/regressiontests/signing/tests.py30
-rw-r--r--tests/regressiontests/sites_framework/models.py4
-rw-r--r--tests/regressiontests/staticfiles_tests/tests.py18
-rw-r--r--tests/regressiontests/string_lookup/models.py19
-rw-r--r--tests/regressiontests/syndication/models.py7
-rw-r--r--tests/regressiontests/templates/filters.py19
-rw-r--r--tests/regressiontests/templates/response.py34
-rw-r--r--tests/regressiontests/templates/tests.py26
-rw-r--r--tests/regressiontests/test_client_regress/tests.py4
-rw-r--r--tests/regressiontests/test_client_regress/views.py4
-rw-r--r--tests/regressiontests/test_utils/tests.py33
-rw-r--r--tests/regressiontests/urlpatterns_reverse/tests.py36
-rw-r--r--tests/regressiontests/urlpatterns_reverse/views_broken.py2
-rw-r--r--tests/regressiontests/utils/archive.py6
-rw-r--r--tests/regressiontests/utils/crypto.py12
-rw-r--r--tests/regressiontests/utils/dateparse.py44
-rw-r--r--tests/regressiontests/utils/encoding.py17
-rw-r--r--tests/regressiontests/utils/html.py9
-rw-r--r--tests/regressiontests/utils/module_loading.py13
-rw-r--r--tests/regressiontests/utils/tests.py34
-rw-r--r--tests/regressiontests/utils/text.py8
-rw-r--r--tests/regressiontests/views/models.py7
-rw-r--r--tests/regressiontests/views/tests/debug.py2
-rw-r--r--tests/regressiontests/views/tests/i18n.py30
-rw-r--r--tests/regressiontests/views/tests/shortcuts.py14
-rw-r--r--tests/regressiontests/wsgi/tests.py4
410 files changed, 5815 insertions, 2567 deletions
diff --git a/AUTHORS b/AUTHORS
index 6ca8b297f4..5fa957885c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -373,6 +373,7 @@ answer newbie questions, and generally made Django that much better:
michael.mcewan@gmail.com
Paul McLanahan <paul@mclanahan.net>
Tobias McNulty <http://www.caktusgroup.com/blog>
+ Andrews Medina <andrewsmedina@gmail.com>
Zain Memon
Christian Metts
michal@plovarna.cz
@@ -467,6 +468,7 @@ answer newbie questions, and generally made Django that much better:
Vinay Sajip <vinay_sajip@yahoo.co.uk>
Bartolome Sanchez Salado <i42sasab@uco.es>
Kadesarin Sanjek
+ Tim Saylor <tim.saylor@gmail.com>
Massimo Scamarcia <massimo.scamarcia@gmail.com>
Paulo Scardine <paulo@scardine.com.br>
David Schein
diff --git a/MANIFEST.in b/MANIFEST.in
index 2dde740b06..185e57646a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
-include README
+include README.rst
include AUTHORS
include INSTALL
include LICENSE
diff --git a/django/bin/django-2to3.py b/django/bin/django-2to3.py
new file mode 100755
index 0000000000..35566abb94
--- /dev/null
+++ b/django/bin/django-2to3.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+# This works exactly like 2to3, except that it uses Django's fixers rather
+# than 2to3's built-in fixers.
+
+import sys
+from lib2to3.main import main
+
+sys.exit(main("django.utils.2to3_fixes"))
+
diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index e1c3fd1808..f4d17ca9f3 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -152,17 +152,25 @@ class UserSettingsHolder(BaseSettings):
Requests for configuration variables not in this class are satisfied
from the module specified in default_settings (if possible).
"""
+ self.__dict__['_deleted'] = set()
self.default_settings = default_settings
def __getattr__(self, name):
+ if name in self._deleted:
+ raise AttributeError
return getattr(self.default_settings, name)
+ def __setattr__(self, name, value):
+ self._deleted.discard(name)
+ return super(UserSettingsHolder, self).__setattr__(name, value)
+
+ def __delattr__(self, name):
+ self._deleted.add(name)
+ return super(UserSettingsHolder, self).__delattr__(name)
+
def __dir__(self):
return list(self.__dict__) + dir(self.default_settings)
- # For Python < 2.6:
- __members__ = property(lambda self: self.__dir__())
-
settings = LazySettings()
diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py
index 99590d6fd5..6bdaa34988 100644
--- a/django/conf/project_template/project_name/settings.py
+++ b/django/conf/project_template/project_name/settings.py
@@ -13,10 +13,11 @@ DATABASES = {
'default': {
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '', # Or path to database file if using sqlite3.
- 'USER': '', # Not used with sqlite3.
- 'PASSWORD': '', # Not used with sqlite3.
- 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
- 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ # The following settings are not used with sqlite3:
+ 'USER': '',
+ 'PASSWORD': '',
+ 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
+ 'PORT': '', # Set to empty string for default.
}
}
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
index e31c6d84ed..2b12edd4e2 100644
--- a/django/contrib/admin/models.py
+++ b/django/contrib/admin/models.py
@@ -6,6 +6,7 @@ from django.contrib.auth.models import User
from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text
+from django.utils.encoding import python_2_unicode_compatible
ADDITION = 1
CHANGE = 2
@@ -16,6 +17,7 @@ class LogEntryManager(models.Manager):
e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message)
e.save()
+@python_2_unicode_compatible
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(User)
@@ -36,7 +38,7 @@ class LogEntry(models.Model):
def __repr__(self):
return smart_text(self.action_time)
- def __unicode__(self):
+ def __str__(self):
if self.action_flag == ADDITION:
return _('Added "%(object)s".') % {'object': self.object_repr}
elif self.action_flag == CHANGE:
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 82d7296c85..e27875cdad 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -29,7 +29,7 @@
{% if change %}{% if not is_popup %}
<ul class="object-tools">
{% block object-tools-items %}
- <li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+ <li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li>
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %}
</ul>
diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py
index 888b5ed9c3..463e0792f0 100644
--- a/django/contrib/admin/templatetags/log.py
+++ b/django/contrib/admin/templatetags/log.py
@@ -17,7 +17,7 @@ class AdminLogNode(template.Node):
user_id = self.user
if not user_id.isdigit():
user_id = context[self.user].id
- context[self.varname] = LogEntry.objects.filter(user__id__exact=user_id).select_related('content_type', 'user')[:self.limit]
+ context[self.varname] = LogEntry.objects.filter(user__id__exact=user_id).select_related('content_type', 'user')[:int(self.limit)]
return ''
@register.tag
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index ff90e1d007..889f692ac3 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -12,7 +12,7 @@ from django.utils import formats
from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils import timezone
-from django.utils.encoding import force_text, smart_text, smart_bytes
+from django.utils.encoding import force_str, force_text, smart_text
from django.utils import six
from django.utils.translation import ungettext
from django.core.urlresolvers import reverse
@@ -277,7 +277,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
label = force_text(model._meta.verbose_name)
attr = six.text_type
elif name == "__str__":
- label = smart_bytes(model._meta.verbose_name)
+ label = force_str(model._meta.verbose_name)
attr = bytes
else:
if callable(name):
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 3eabf3dbeb..74ef095b4b 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -6,7 +6,7 @@ from django.core.paginator import InvalidPage
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils.datastructures import SortedDict
-from django.utils.encoding import force_text, smart_bytes
+from django.utils.encoding import force_str, force_text
from django.utils.translation import ugettext, ugettext_lazy
from django.utils.http import urlencode
@@ -94,7 +94,7 @@ class ChangeList(object):
# 'key' will be used as a keyword argument later, so Python
# requires it to be a string.
del lookup_params[key]
- lookup_params[smart_bytes(key)] = value
+ lookup_params[force_str(key)] = value
if not self.model_admin.lookup_allowed(key, value):
raise SuspiciousOperation("Filtering by %s not allowed" % key)
@@ -148,7 +148,7 @@ class ChangeList(object):
if remove is None: remove = []
p = self.params.copy()
for r in remove:
- for k in p.keys():
+ for k in list(p):
if k.startswith(r):
del p[k]
for k, v in new_params.items():
diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py
index 0e10eb4fa3..9be0093dfc 100644
--- a/django/contrib/admindocs/utils.py
+++ b/django/contrib/admindocs/utils.py
@@ -6,7 +6,7 @@ from email.errors import HeaderParseError
from django.utils.safestring import mark_safe
from django.core.urlresolvers import reverse
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_bytes
try:
import docutils.core
import docutils.nodes
@@ -66,7 +66,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None):
"link_base" : reverse('django-admindocs-docroot').rstrip('/')
}
if thing_being_parsed:
- thing_being_parsed = smart_bytes("<%s>" % thing_being_parsed)
+ thing_being_parsed = force_bytes("<%s>" % thing_being_parsed)
parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
destination_path=None, writer_name='html',
settings_overrides=overrides)
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index 7a608d0777..beeb284998 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -7,6 +7,7 @@ from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied
from django.utils.decorators import available_attrs
+from django.utils.encoding import force_str
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
@@ -22,9 +23,11 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
if test_func(request.user):
return view_func(request, *args, **kwargs)
path = request.build_absolute_uri()
+ # urlparse chokes on lazy objects in Python 3
+ login_url_as_str = force_str(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
- login_scheme, login_netloc = urlparse(login_url or settings.LOGIN_URL)[:2]
+ login_scheme, login_netloc = urlparse(login_url_as_str)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index dfd039f018..75b3ca4ece 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -63,16 +63,16 @@ class UserCreationForm(forms.ModelForm):
}
username = forms.RegexField(label=_("Username"), max_length=30,
regex=r'^[\w.@+-]+$',
- help_text = _("Required. 30 characters or fewer. Letters, digits and "
+ help_text=_("Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."),
- error_messages = {
+ error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput,
- help_text = _("Enter the same password as above, for verification."))
+ help_text=_("Enter the same password as above, for verification."))
class Meta:
model = User
@@ -107,9 +107,9 @@ class UserCreationForm(forms.ModelForm):
class UserChangeForm(forms.ModelForm):
username = forms.RegexField(
label=_("Username"), max_length=30, regex=r"^[\w.@+-]+$",
- help_text = _("Required. 30 characters or fewer. Letters, digits and "
+ help_text=_("Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."),
- error_messages = {
+ error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
password = ReadOnlyPasswordHashField(label=_("Password"),
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 45c1f88ab2..bd0c6778c9 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -8,7 +8,7 @@ from django.conf import settings
from django.test.signals import setting_changed
from django.utils import importlib
from django.utils.datastructures import SortedDict
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_bytes
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import (
pbkdf2, constant_time_compare, get_random_string)
@@ -219,7 +219,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
if not iterations:
iterations = self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest)
- hash = base64.b64encode(hash).strip()
+ hash = base64.b64encode(hash).decode('ascii').strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def verify(self, password, encoded):
@@ -299,7 +299,7 @@ class SHA1PasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
assert password
assert salt and '$' not in salt
- hash = hashlib.sha1(smart_bytes(salt + password)).hexdigest()
+ hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded):
@@ -327,7 +327,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
assert password
assert salt and '$' not in salt
- hash = hashlib.md5(smart_bytes(salt + password)).hexdigest()
+ hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded):
@@ -361,7 +361,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
return ''
def encode(self, password, salt):
- return hashlib.md5(smart_bytes(password)).hexdigest()
+ return hashlib.md5(force_bytes(password)).hexdigest()
def verify(self, password, encoded):
encoded_2 = self.encode(password, '')
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index 7abd2abcf4..23a053d985 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -9,6 +9,7 @@ import unicodedata
from django.contrib.auth import models as auth_app
from django.db.models import get_models, signals
from django.contrib.auth.models import User
+from django.utils import six
from django.utils.six.moves import input
@@ -85,13 +86,22 @@ def get_system_username():
username could not be determined.
"""
try:
- return getpass.getuser().decode(locale.getdefaultlocale()[1])
- except (ImportError, KeyError, UnicodeDecodeError):
+ result = getpass.getuser()
+ except (ImportError, KeyError):
# KeyError will be raised by os.getpwuid() (called by getuser())
# if there is no corresponding entry in the /etc/passwd file
# (a very restricted chroot environment, for example).
- # UnicodeDecodeError - preventive treatment for non-latin Windows.
return ''
+ if not six.PY3:
+ default_locale = locale.getdefaultlocale()[1]
+ if not default_locale:
+ return ''
+ try:
+ result = result.decode(default_locale)
+ except UnicodeDecodeError:
+ # UnicodeDecodeError - preventive treatment for non-latin Windows.
+ return ''
+ return result
def get_default_username(check_db=True):
@@ -108,7 +118,7 @@ def get_default_username(check_db=True):
default_username = get_system_username()
try:
default_username = unicodedata.normalize('NFKD', default_username)\
- .encode('ascii', 'ignore').replace(' ', '').lower()
+ .encode('ascii', 'ignore').decode('ascii').replace(' ', '').lower()
except UnicodeDecodeError:
return ''
if not RE_VALID_USERNAME.match(default_username):
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 1099aa195b..1c21917a8c 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -16,6 +16,7 @@ from django.contrib.auth.hashers import (
check_password, make_password, is_password_usable, UNUSABLE_PASSWORD)
from django.contrib.auth.signals import user_logged_in
from django.contrib.contenttypes.models import ContentType
+from django.utils.encoding import python_2_unicode_compatible
def update_last_login(sender, user, **kwargs):
@@ -41,6 +42,7 @@ class PermissionManager(models.Manager):
)
+@python_2_unicode_compatible
class Permission(models.Model):
"""
The permissions system provides a way to assign permissions to specific
@@ -76,7 +78,7 @@ class Permission(models.Model):
ordering = ('content_type__app_label', 'content_type__model',
'codename')
- def __unicode__(self):
+ def __str__(self):
return "%s | %s | %s" % (
six.text_type(self.content_type.app_label),
six.text_type(self.content_type),
@@ -94,6 +96,7 @@ class GroupManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
+@python_2_unicode_compatible
class Group(models.Model):
"""
Groups are a generic way of categorizing users to apply permissions, or
@@ -121,7 +124,7 @@ class Group(models.Model):
verbose_name = _('group')
verbose_name_plural = _('groups')
- def __unicode__(self):
+ def __str__(self):
return self.name
def natural_key(self):
@@ -221,6 +224,7 @@ def _user_has_module_perms(user, app_label):
return False
+@python_2_unicode_compatible
class User(models.Model):
"""
Users within the Django authentication system are represented by this
@@ -259,7 +263,7 @@ class User(models.Model):
verbose_name = _('user')
verbose_name_plural = _('users')
- def __unicode__(self):
+ def __str__(self):
return self.username
def natural_key(self):
@@ -403,6 +407,7 @@ class User(models.Model):
return self._profile_cache
+@python_2_unicode_compatible
class AnonymousUser(object):
id = None
pk = None
@@ -416,11 +421,8 @@ class AnonymousUser(object):
def __init__(self):
pass
- def __unicode__(self):
- return 'AnonymousUser'
-
def __str__(self):
- return six.text_type(self).encode('utf-8')
+ return 'AnonymousUser'
def __eq__(self, other):
return isinstance(other, self.__class__)
diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py
index 21acb2004f..710754b8f1 100644
--- a/django/contrib/auth/tests/basic.py
+++ b/django/contrib/auth/tests/basic.py
@@ -1,13 +1,11 @@
+import locale
+import traceback
+
+from django.contrib.auth.management.commands import createsuperuser
from django.contrib.auth.models import User, AnonymousUser
from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
-from django.utils.unittest import skipUnless
-
-try:
- import crypt as crypt_module
-except ImportError:
- crypt_module = None
class BasicTestCase(TestCase):
@@ -111,3 +109,37 @@ class BasicTestCase(TestCase):
u = User.objects.get(username="joe+admin@somewhere.org")
self.assertEqual(u.email, 'joe@somewhere.org')
self.assertFalse(u.has_usable_password())
+
+ def test_createsuperuser_nolocale(self):
+ """
+ Check that createsuperuser does not break when no locale is set. See
+ ticket #16017.
+ """
+
+ old_getdefaultlocale = locale.getdefaultlocale
+ old_getpass = createsuperuser.getpass
+ try:
+ # Temporarily remove locale information
+ locale.getdefaultlocale = lambda: (None, None)
+
+ # Temporarily replace getpass to allow interactive code to be used
+ # non-interactively
+ class mock_getpass: pass
+ mock_getpass.getpass = staticmethod(lambda p=None: "nopasswd")
+ createsuperuser.getpass = mock_getpass
+
+ # Call the command in this new environment
+ new_io = StringIO()
+ call_command("createsuperuser", interactive=True, username="nolocale@somewhere.org", email="nolocale@somewhere.org", stdout=new_io)
+
+ except TypeError as e:
+ self.fail("createsuperuser fails if the OS provides no information about the current locale")
+
+ finally:
+ # Re-apply locale and getpass information
+ createsuperuser.getpass = old_getpass
+ locale.getdefaultlocale = old_getdefaultlocale
+
+ # If we were successful, a user should have been created
+ u = User.objects.get(username="nolocale@somewhere.org")
+ self.assertEqual(u.email, 'nolocale@somewhere.org')
diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py
index c98b7491c8..ac83086dc3 100644
--- a/django/contrib/auth/tests/management.py
+++ b/django/contrib/auth/tests/management.py
@@ -4,16 +4,20 @@ from django.contrib.auth import models, management
from django.contrib.auth.management.commands import changepassword
from django.core.management.base import CommandError
from django.test import TestCase
+from django.utils import six
from django.utils.six import StringIO
class GetDefaultUsernameTestCase(TestCase):
def setUp(self):
- self._getpass_getuser = management.get_system_username
+ self.old_get_system_username = management.get_system_username
def tearDown(self):
- management.get_system_username = self._getpass_getuser
+ management.get_system_username = self.old_get_system_username
+
+ def test_actual_implementation(self):
+ self.assertIsInstance(management.get_system_username(), six.text_type)
def test_simple(self):
management.get_system_username = lambda: 'joe'
diff --git a/django/contrib/auth/tests/tokens.py b/django/contrib/auth/tests/tokens.py
index beccfc5d07..44117a4f84 100644
--- a/django/contrib/auth/tests/tokens.py
+++ b/django/contrib/auth/tests/tokens.py
@@ -1,9 +1,11 @@
+import sys
from datetime import date, timedelta
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.test import TestCase
+from django.utils import unittest
class TokenGeneratorTest(TestCase):
@@ -51,6 +53,7 @@ class TokenGeneratorTest(TestCase):
p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1))
self.assertFalse(p2.check_token(user, tk1))
+ @unittest.skipIf(sys.version_info[:2] >= (3, 0), "Unnecessary test with Python 3")
def test_date_length(self):
"""
Make sure we don't allow overly long dates, causing a potential DoS.
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index ccfc7a1003..f93541b4bf 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse
+from django.utils.encoding import force_str
from django.utils.http import base36_to_int
from django.utils.translation import ugettext as _
from django.views.decorators.debug import sensitive_post_parameters
@@ -116,10 +117,10 @@ def redirect_to_login(next, login_url=None,
"""
Redirects the user to the login page, passing the given 'next' page
"""
- if not login_url:
- login_url = settings.LOGIN_URL
+ # urlparse chokes on lazy objects in Python 3
+ login_url_as_str = force_str(login_url or settings.LOGIN_URL)
- login_url_parts = list(urlparse(login_url))
+ login_url_parts = list(urlparse(login_url_as_str))
if redirect_field_name:
querystring = QueryDict(login_url_parts[4], mutable=True)
querystring[redirect_field_name] = next
@@ -200,7 +201,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
try:
uid_int = base36_to_int(uidb36)
user = User.objects.get(id=uid_int)
- except (ValueError, User.DoesNotExist):
+ except (ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py
index 475b3c8dea..b043b4187a 100644
--- a/django/contrib/comments/models.py
+++ b/django/contrib/comments/models.py
@@ -8,6 +8,7 @@ from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.conf import settings
+from django.utils.encoding import python_2_unicode_compatible
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
@@ -39,6 +40,7 @@ class BaseCommentAbstractModel(models.Model):
args=(self.content_type_id, self.object_pk)
)
+@python_2_unicode_compatible
class Comment(BaseCommentAbstractModel):
"""
A user comment about some object.
@@ -76,7 +78,7 @@ class Comment(BaseCommentAbstractModel):
verbose_name = _('comment')
verbose_name_plural = _('comments')
- def __unicode__(self):
+ def __str__(self):
return "%s: %s..." % (self.name, self.comment[:50])
def save(self, *args, **kwargs):
@@ -153,6 +155,7 @@ class Comment(BaseCommentAbstractModel):
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
+@python_2_unicode_compatible
class CommentFlag(models.Model):
"""
Records a flag on a comment. This is intentionally flexible; right now, a
@@ -182,7 +185,7 @@ class CommentFlag(models.Model):
verbose_name = _('comment flag')
verbose_name_plural = _('comment flags')
- def __unicode__(self):
+ def __str__(self):
return "%s flag of comment ID %s by %s" % \
(self.flag, self.comment_id, self.user.username)
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index e6d547a491..b658655bbb 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -1,6 +1,7 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text, force_text
+from django.utils.encoding import python_2_unicode_compatible
class ContentTypeManager(models.Manager):
@@ -122,6 +123,7 @@ class ContentTypeManager(models.Manager):
self.__class__._cache.setdefault(using, {})[key] = ct
self.__class__._cache.setdefault(using, {})[ct.id] = ct
+@python_2_unicode_compatible
class ContentType(models.Model):
name = models.CharField(max_length=100)
app_label = models.CharField(max_length=100)
@@ -135,7 +137,7 @@ class ContentType(models.Model):
ordering = ('name',)
unique_together = (('app_label', 'model'),)
- def __unicode__(self):
+ def __str__(self):
# self.name is deprecated in favor of using model's verbose_name, which
# can be translated. Formal deprecation is delayed until we have DB
# migration to be able to remove the field from the database along with
diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py
index cfd7e6ff32..2f92a34581 100644
--- a/django/contrib/contenttypes/tests.py
+++ b/django/contrib/contenttypes/tests.py
@@ -8,6 +8,7 @@ from django.http import HttpRequest, Http404
from django.test import TestCase
from django.utils.http import urlquote
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class ConcreteModel(models.Model):
@@ -17,13 +18,14 @@ class ProxyModel(ConcreteModel):
class Meta:
proxy = True
+@python_2_unicode_compatible
class FooWithoutUrl(models.Model):
"""
Fake model not defining ``get_absolute_url`` for
:meth:`ContentTypesTests.test_shortcut_view_without_get_absolute_url`"""
name = models.CharField(max_length=30, unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py
index 810e039894..5f5f46f0d1 100644
--- a/django/contrib/databrowse/datastructures.py
+++ b/django/contrib/databrowse/datastructures.py
@@ -7,8 +7,9 @@ from __future__ import unicode_literals
from django.db import models
from django.utils import formats
from django.utils.text import capfirst
-from django.utils.encoding import smart_text, smart_bytes, iri_to_uri
+from django.utils.encoding import smart_text, force_str, iri_to_uri
from django.db.models.query import QuerySet
+from django.utils.encoding import python_2_unicode_compatible
EMPTY_VALUE = '(None)'
DISPLAY_SIZE = 100
@@ -22,7 +23,7 @@ class EasyModel(object):
self.verbose_name_plural = model._meta.verbose_name_plural
def __repr__(self):
- return '<EasyModel for %s>' % smart_bytes(self.model._meta.object_name)
+ return force_str('<EasyModel for %s>' % self.model._meta.object_name)
def model_databrowse(self):
"Returns the ModelDatabrowse class for this model."
@@ -61,7 +62,7 @@ class EasyField(object):
self.model, self.field = easy_model, field
def __repr__(self):
- return smart_bytes('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+ return force_str('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def choices(self):
for value, label in self.field.choices:
@@ -79,27 +80,25 @@ class EasyChoice(object):
self.value, self.label = value, label
def __repr__(self):
- return smart_bytes('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+ return force_str('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def url(self):
return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))
+@python_2_unicode_compatible
class EasyInstance(object):
def __init__(self, easy_model, instance):
self.model, self.instance = easy_model, instance
def __repr__(self):
- return smart_bytes('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
+ return force_str('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
- def __unicode__(self):
+ def __str__(self):
val = smart_text(self.instance)
if len(val) > DISPLAY_SIZE:
return val[:DISPLAY_SIZE] + '...'
return val
- def __str__(self):
- return self.__unicode__().encode('utf-8')
-
def pk(self):
return self.instance._get_pk_val()
@@ -136,7 +135,7 @@ class EasyInstanceField(object):
self.raw_value = getattr(instance.instance, field.name)
def __repr__(self):
- return smart_bytes('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+ return force_str('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def values(self):
"""
diff --git a/django/contrib/databrowse/tests.py b/django/contrib/databrowse/tests.py
index 149383cf72..d649b4af67 100644
--- a/django/contrib/databrowse/tests.py
+++ b/django/contrib/databrowse/tests.py
@@ -1,26 +1,30 @@
from django.contrib import databrowse
from django.db import models
from django.test import TestCase
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class SomeModel(models.Model):
some_field = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.some_field
+@python_2_unicode_compatible
class SomeOtherModel(models.Model):
some_other_field = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.some_other_field
+@python_2_unicode_compatible
class YetAnotherModel(models.Model):
yet_another_field = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.yet_another_field
diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
index 42ec155f34..3a5b4d6135 100644
--- a/django/contrib/flatpages/models.py
+++ b/django/contrib/flatpages/models.py
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
from django.db import models
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class FlatPage(models.Model):
url = models.CharField(_('URL'), max_length=100, db_index=True)
title = models.CharField(_('title'), max_length=200)
@@ -21,7 +23,7 @@ class FlatPage(models.Model):
verbose_name_plural = _('flat pages')
ordering = ('url',)
- def __unicode__(self):
+ def __str__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py
index ee93479cbd..15941332ed 100644
--- a/django/contrib/formtools/tests/__init__.py
+++ b/django/contrib/formtools/tests/__init__.py
@@ -1,6 +1,9 @@
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import datetime
import os
+import pickle
import re
import warnings
@@ -16,6 +19,7 @@ from django.contrib.formtools.tests.wizard import *
from django.contrib.formtools.tests.forms import *
success_string = "Done was called!"
+success_string_encoded = success_string.encode()
class TestFormPreview(preview.FormPreview):
def get_context(self, request, form):
@@ -78,7 +82,7 @@ class PreviewTests(TestCase):
"""
# Pass strings for form submittal and add stage variable to
# show we previously saw first stage of the form.
- self.test_data.update({'stage': 1})
+ self.test_data.update({'stage': 1, 'date1': datetime.date(2006, 10, 25)})
response = self.client.post('/preview/', self.test_data)
# Check to confirm stage is set to 2 in output form.
stage = self.input % 2
@@ -96,13 +100,13 @@ class PreviewTests(TestCase):
"""
# Pass strings for form submittal and add stage variable to
# show we previously saw first stage of the form.
- self.test_data.update({'stage':2})
+ self.test_data.update({'stage': 2, 'date1': datetime.date(2006, 10, 25)})
response = self.client.post('/preview/', self.test_data)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
hash = self.preview.security_hash(None, TestForm(self.test_data))
self.test_data.update({'hash': hash})
response = self.client.post('/preview/', self.test_data)
- self.assertEqual(response.content, success_string)
+ self.assertEqual(response.content, success_string_encoded)
def test_bool_submit(self):
"""
@@ -122,7 +126,7 @@ class PreviewTests(TestCase):
self.test_data.update({'hash': hash, 'bool1': 'False'})
with warnings.catch_warnings(record=True):
response = self.client.post('/preview/', self.test_data)
- self.assertEqual(response.content, success_string)
+ self.assertEqual(response.content, success_string_encoded)
def test_form_submit_good_hash(self):
"""
@@ -133,11 +137,11 @@ class PreviewTests(TestCase):
# show we previously saw first stage of the form.
self.test_data.update({'stage':2})
response = self.client.post('/preview/', self.test_data)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
hash = utils.form_hmac(TestForm(self.test_data))
self.test_data.update({'hash': hash})
response = self.client.post('/preview/', self.test_data)
- self.assertEqual(response.content, success_string)
+ self.assertEqual(response.content, success_string_encoded)
def test_form_submit_bad_hash(self):
@@ -150,11 +154,11 @@ class PreviewTests(TestCase):
self.test_data.update({'stage':2})
response = self.client.post('/preview/', self.test_data)
self.assertEqual(response.status_code, 200)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
hash = utils.form_hmac(TestForm(self.test_data)) + "bad"
self.test_data.update({'hash': hash})
response = self.client.post('/previewpreview/', self.test_data)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
class FormHmacTests(unittest.TestCase):
@@ -165,8 +169,8 @@ class FormHmacTests(unittest.TestCase):
leading/trailing whitespace so as to be friendly to broken browsers that
submit it (usually in textareas).
"""
- f1 = HashTestForm({'name': 'joe', 'bio': 'Nothing notable.'})
- f2 = HashTestForm({'name': ' joe', 'bio': 'Nothing notable. '})
+ f1 = HashTestForm({'name': 'joe', 'bio': 'Speaking español.'})
+ f2 = HashTestForm({'name': ' joe', 'bio': 'Speaking español. '})
hash1 = utils.form_hmac(f1)
hash2 = utils.form_hmac(f2)
self.assertEqual(hash1, hash2)
@@ -270,7 +274,10 @@ class WizardTests(TestCase):
"""
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
response = self.client.post('/wizard1/', data)
self.assertEqual(2, response.context['step0'])
@@ -295,15 +302,24 @@ class WizardTests(TestCase):
wizard = WizardWithProcessStep([WizardPageOneForm])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
- "hash_1": "1e6f6315da42e62f33a30640ec7e007ad3fbf1a1",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
+ "hash_1": {
+ 2: "1e6f6315da42e62f33a30640ec7e007ad3fbf1a1",
+ 3: "c33142ef9d01b1beae238adf22c3c6c57328f51a",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "2"}
self.assertRaises(http.Http404, wizard, DummyRequest(POST=data))
@@ -325,7 +341,10 @@ class WizardTests(TestCase):
WizardPageThreeForm])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
@@ -349,7 +368,10 @@ class WizardTests(TestCase):
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
@@ -375,7 +397,10 @@ class WizardTests(TestCase):
WizardPageThreeForm])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
diff --git a/django/contrib/formtools/tests/forms.py b/django/contrib/formtools/tests/forms.py
index 9f6f596d3c..1ed2ab48bb 100644
--- a/django/contrib/formtools/tests/forms.py
+++ b/django/contrib/formtools/tests/forms.py
@@ -21,6 +21,7 @@ class TestForm(forms.Form):
field1 = forms.CharField()
field1_ = forms.CharField()
bool1 = forms.BooleanField(required=False)
+ date1 = forms.DateField(required=False)
class HashTestForm(forms.Form):
name = forms.CharField()
diff --git a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
index a860edd9e9..7529d89a2c 100644
--- a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
+++ b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
@@ -122,6 +122,7 @@ class NamedWizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(
reverse(self.wizard_urlname,
@@ -149,7 +150,9 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['form_list']
- self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data[1]['file1'].read(), f.read())
+ all_data[1]['file1'].close()
del all_data[1]['file1']
self.assertEqual(all_data, [
{'name': 'Pony', 'thirsty': True, 'user': self.testuser},
@@ -182,9 +185,10 @@ class NamedWizardTests(object):
response = self.client.get(step2_url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
- self.assertEqual(
- response.context['wizard']['form'].files['form2-file1'].read(),
- open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(
+ response.context['wizard']['form'].files['form2-file1'].read(),
+ f.read())
response = self.client.post(
reverse(self.wizard_urlname,
@@ -201,7 +205,9 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data']
- self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data['file1'].read(), f.read())
+ all_data['file1'].close()
del all_data['file1']
self.assertEqual(
all_data,
@@ -225,6 +231,7 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(
reverse(self.wizard_urlname,
diff --git a/django/contrib/formtools/tests/wizard/wizardtests/tests.py b/django/contrib/formtools/tests/wizard/wizardtests/tests.py
index a35664b322..586bd59341 100644
--- a/django/contrib/formtools/tests/wizard/wizardtests/tests.py
+++ b/django/contrib/formtools/tests/wizard/wizardtests/tests.py
@@ -95,7 +95,9 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['form_list']
- self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data[1]['file1'].read(), f.read())
+ all_data[1]['file1'].close()
del all_data[1]['file1']
self.assertEqual(all_data, [
{'name': 'Pony', 'thirsty': True, 'user': self.testuser},
@@ -112,8 +114,9 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1]
- post_data['form2-file1'] = open(__file__, 'rb')
- response = self.client.post(self.wizard_url, post_data)
+ with open(__file__, 'rb') as post_file:
+ post_data['form2-file1'] = post_file
+ response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
response = self.client.post(self.wizard_url, self.wizard_step_data[2])
@@ -123,7 +126,9 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data']
- self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data['file1'].read(), f.read())
+ all_data['file1'].close()
del all_data['file1']
self.assertEqual(all_data, {
'name': 'Pony', 'thirsty': True, 'user': self.testuser,
@@ -140,6 +145,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
@@ -167,6 +173,7 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
diff --git a/django/contrib/formtools/utils.py b/django/contrib/formtools/utils.py
index 8763cded07..76277c6b49 100644
--- a/django/contrib/formtools/utils.py
+++ b/django/contrib/formtools/utils.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
# Do not try cPickle here (see #18340)
import pickle
diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py
index 05c9f6f121..aafc833484 100644
--- a/django/contrib/formtools/wizard/storage/base.py
+++ b/django/contrib/formtools/wizard/storage/base.py
@@ -1,6 +1,5 @@
from django.core.files.uploadedfile import UploadedFile
from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import smart_bytes
from django.utils.functional import lazy_property
from django.utils import six
@@ -74,8 +73,7 @@ class BaseStorage(object):
files = {}
for field, field_dict in six.iteritems(wizard_files):
- field_dict = dict((smart_bytes(k), v)
- for k, v in six.iteritems(field_dict))
+ field_dict = field_dict.copy()
tmp_name = field_dict.pop('tmp_name')
files[field] = UploadedFile(
file=self.file_storage.open(tmp_name), **field_dict)
diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py
index d9f3546cff..f7af420a8d 100644
--- a/django/contrib/gis/db/backends/base.py
+++ b/django/contrib/gis/db/backends/base.py
@@ -5,6 +5,7 @@ Base/mixin classes for the spatial backend database operations and the
import re
from django.contrib.gis import gdal
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class BaseSpatialOperations(object):
"""
@@ -131,6 +132,7 @@ class BaseSpatialOperations(object):
def spatial_ref_sys(self):
raise NotImplementedError
+@python_2_unicode_compatible
class SpatialRefSysMixin(object):
"""
The SpatialRefSysMixin is a class used by the database-dependent
@@ -325,7 +327,7 @@ class SpatialRefSysMixin(object):
radius, flattening = sphere_params
return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
- def __unicode__(self):
+ def __str__(self):
"""
Returns the string representation. If GDAL is installed,
it will be 'pretty' OGC WKT.
diff --git a/django/contrib/gis/db/backends/oracle/models.py b/django/contrib/gis/db/backends/oracle/models.py
index ed29f7bb38..b7deb3a946 100644
--- a/django/contrib/gis/db/backends/oracle/models.py
+++ b/django/contrib/gis/db/backends/oracle/models.py
@@ -9,7 +9,9 @@
"""
from django.contrib.gis.db import models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GeometryColumns(models.Model):
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
table_name = models.CharField(max_length=32)
@@ -36,7 +38,7 @@ class GeometryColumns(models.Model):
"""
return 'column_name'
- def __unicode__(self):
+ def __str__(self):
return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
class SpatialRefSys(models.Model, SpatialRefSysMixin):
diff --git a/django/contrib/gis/db/backends/postgis/models.py b/django/contrib/gis/db/backends/postgis/models.py
index a38598343c..e8052594c6 100644
--- a/django/contrib/gis/db/backends/postgis/models.py
+++ b/django/contrib/gis/db/backends/postgis/models.py
@@ -3,7 +3,9 @@
"""
from django.db import models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GeometryColumns(models.Model):
"""
The 'geometry_columns' table from the PostGIS. See the PostGIS
@@ -37,7 +39,7 @@ class GeometryColumns(models.Model):
"""
return 'f_geometry_column'
- def __unicode__(self):
+ def __str__(self):
return "%s.%s - %dD %s field (SRID: %d)" % \
(self.f_table_name, self.f_geometry_column,
self.coord_dimension, self.type, self.srid)
diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py
index d0a5f82033..27332b9b57 100644
--- a/django/contrib/gis/db/backends/spatialite/creation.py
+++ b/django/contrib/gis/db/backends/spatialite/creation.py
@@ -30,6 +30,7 @@ class SpatiaLiteCreation(DatabaseCreation):
self.connection.close()
self.connection.settings_dict["NAME"] = test_database_name
+ self.connection.ops.confirm_spatial_components_versions()
# Need to load the SpatiaLite initialization SQL before running `syncdb`.
self.load_spatialite_sql()
diff --git a/django/contrib/gis/db/backends/spatialite/models.py b/django/contrib/gis/db/backends/spatialite/models.py
index 684c5d8fc7..b281f0bc62 100644
--- a/django/contrib/gis/db/backends/spatialite/models.py
+++ b/django/contrib/gis/db/backends/spatialite/models.py
@@ -3,7 +3,9 @@
"""
from django.db import models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GeometryColumns(models.Model):
"""
The 'geometry_columns' table from SpatiaLite.
@@ -35,7 +37,7 @@ class GeometryColumns(models.Model):
"""
return 'f_geometry_column'
- def __unicode__(self):
+ def __str__(self):
return "%s.%s - %dD %s field (SRID: %d)" % \
(self.f_table_name, self.f_geometry_column,
self.coord_dimension, self.type, self.srid)
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 60fe0a8069..80f05ef076 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -113,6 +113,12 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
def __init__(self, connection):
super(DatabaseOperations, self).__init__(connection)
+ # Creating the GIS terms dictionary.
+ gis_terms = ['isnull']
+ gis_terms += self.geometry_functions.keys()
+ self.gis_terms = dict([(term, None) for term in gis_terms])
+
+ def confirm_spatial_components_versions(self):
# Determine the version of the SpatiaLite library.
try:
vtup = self.spatialite_version_tuple()
@@ -129,11 +135,6 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
'SQL loaded on this database?' %
(self.connection.settings_dict['NAME'], msg))
- # Creating the GIS terms dictionary.
- gis_terms = ['isnull']
- gis_terms += list(self.geometry_functions)
- self.gis_terms = dict([(term, None) for term in gis_terms])
-
if version >= (2, 4, 0):
# Spatialite 2.4.0-RC4 added AsGML and AsKML, however both
# RC2 (shipped in popular Debian/Ubuntu packages) and RC4
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index cc61dfa4d2..d87e151aea 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -1,16 +1,15 @@
from django.db import connections
from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet
-from django.utils import six
from django.contrib.gis.db.models import aggregates
from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery
from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Area, Distance
-from django.utils import six
from django.utils import six
+
class GeoQuerySet(QuerySet):
"The Geographic QuerySet."
diff --git a/django/contrib/gis/gdal/tests/__init__.py b/django/contrib/gis/gdal/tests/__init__.py
index eb72a38775..262d294a43 100644
--- a/django/contrib/gis/gdal/tests/__init__.py
+++ b/django/contrib/gis/gdal/tests/__init__.py
@@ -19,7 +19,8 @@ test_suites = [test_driver.suite(),
def suite():
"Builds a test suite for the GDAL tests."
s = TestSuite()
- map(s.addTest, test_suites)
+ for test_suite in test_suites:
+ s.addTest(test_suite)
return s
def run(verbosity=1):
diff --git a/django/contrib/gis/maps/google/overlays.py b/django/contrib/gis/maps/google/overlays.py
index 28603ac422..b82d967da6 100644
--- a/django/contrib/gis/maps/google/overlays.py
+++ b/django/contrib/gis/maps/google/overlays.py
@@ -2,8 +2,10 @@ from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Poly
from django.utils.functional import total_ordering
from django.utils.safestring import mark_safe
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GEvent(object):
"""
A Python wrapper for the Google GEvent object.
@@ -48,10 +50,11 @@ class GEvent(object):
self.event = event
self.action = action
- def __unicode__(self):
+ def __str__(self):
"Returns the parameter part of a GEvent."
return mark_safe('"%s", %s' %(self.event, self.action))
+@python_2_unicode_compatible
class GOverlayBase(object):
def __init__(self):
self.events = []
@@ -64,7 +67,7 @@ class GOverlayBase(object):
"Attaches a GEvent to the overlay object."
self.events.append(event)
- def __unicode__(self):
+ def __str__(self):
"The string representation is the JavaScript API call."
return mark_safe('%s(%s)' % (self.__class__.__name__, self.js_params))
diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py
index 8bcdba1b44..36e48f7d20 100644
--- a/django/contrib/gis/sitemaps/views.py
+++ b/django/contrib/gis/sitemaps/views.py
@@ -8,7 +8,6 @@ from django.core.paginator import EmptyPage, PageNotAnInteger
from django.contrib.gis.db.models.fields import GeometryField
from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models import get_model
-from django.utils.encoding import smart_bytes
from django.utils import six
from django.utils.translation import ugettext as _
@@ -61,7 +60,7 @@ def sitemap(request, sitemaps, section=None):
raise Http404(_("Page %s empty") % page)
except PageNotAnInteger:
raise Http404(_("No page '%s'") % page)
- xml = smart_bytes(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}))
+ xml = loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})
return HttpResponse(xml, content_type='application/xml')
def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS):
diff --git a/django/contrib/gis/tests/distapp/models.py b/django/contrib/gis/tests/distapp/models.py
index 76e7d3a03f..bf08829eae 100644
--- a/django/contrib/gis/tests/distapp/models.py
+++ b/django/contrib/gis/tests/distapp/models.py
@@ -1,50 +1,58 @@
from django.contrib.gis.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class SouthTexasCity(models.Model):
"City model on projected coordinate system for South Texas."
name = models.CharField(max_length=30)
point = models.PointField(srid=32140)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class SouthTexasCityFt(models.Model):
"Same City model as above, but U.S. survey feet are the units."
name = models.CharField(max_length=30)
point = models.PointField(srid=2278)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class AustraliaCity(models.Model):
"City model for Australia, using WGS84."
name = models.CharField(max_length=30)
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class CensusZipcode(models.Model):
"Model for a few South Texas ZIP codes (in original Census NAD83)."
name = models.CharField(max_length=5)
poly = models.PolygonField(srid=4269)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class SouthTexasZipcode(models.Model):
"Model for a few South Texas ZIP codes."
name = models.CharField(max_length=5)
poly = models.PolygonField(srid=32140, null=True)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class Interstate(models.Model):
"Geodetic model for U.S. Interstates."
name = models.CharField(max_length=10)
path = models.LineStringField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class SouthTexasInterstate(models.Model):
"Projected model for South Texas Interstates."
name = models.CharField(max_length=10)
path = models.LineStringField(srid=32140)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
diff --git a/django/contrib/gis/tests/geo3d/models.py b/django/contrib/gis/tests/geo3d/models.py
index 3c4f77ee05..81e5f55f78 100644
--- a/django/contrib/gis/tests/geo3d/models.py
+++ b/django/contrib/gis/tests/geo3d/models.py
@@ -1,59 +1,67 @@
from django.contrib.gis.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class City3D(models.Model):
name = models.CharField(max_length=30)
point = models.PointField(dim=3)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Interstate2D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(srid=4269)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Interstate3D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(dim=3, srid=4269)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class InterstateProj2D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(srid=32140)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class InterstateProj3D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(dim=3, srid=32140)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Polygon2D(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(srid=32140)
objects = models.GeoManager()
-
- def __unicode__(self):
+
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Polygon3D(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(dim=3, srid=32140)
objects = models.GeoManager()
-
- def __unicode__(self):
+
+ def __str__(self):
return self.name
class Point2D(models.Model):
diff --git a/django/contrib/gis/tests/geoadmin/models.py b/django/contrib/gis/tests/geoadmin/models.py
index 51a76d1a0e..af0898823d 100644
--- a/django/contrib/gis/tests/geoadmin/models.py
+++ b/django/contrib/gis/tests/geoadmin/models.py
@@ -1,10 +1,12 @@
from django.contrib.gis.db import models
from django.contrib.gis import admin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
admin.site.register(City, admin.OSMGeoAdmin)
diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py
index 79061e1cfc..abde509c8b 100644
--- a/django/contrib/gis/tests/geoapp/models.py
+++ b/django/contrib/gis/tests/geoapp/models.py
@@ -1,20 +1,23 @@
from django.contrib.gis.db import models
from django.contrib.gis.tests.utils import mysql, spatialite
+from django.utils.encoding import python_2_unicode_compatible
# MySQL spatial indices can't handle NULL geometries.
null_flag = not mysql
+@python_2_unicode_compatible
class Country(models.Model):
name = models.CharField(max_length=30)
mpoly = models.MultiPolygonField() # SRID, by default, is 4326
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
# This is an inherited model from City
class PennsylvaniaCity(City):
@@ -22,28 +25,31 @@ class PennsylvaniaCity(City):
founded = models.DateTimeField(null=True)
objects = models.GeoManager() # TODO: This should be implicitly inherited.
+@python_2_unicode_compatible
class State(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(null=null_flag) # Allowing NULL geometries here.
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class Track(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
class Truth(models.Model):
val = models.BooleanField()
objects = models.GeoManager()
if not spatialite:
+ @python_2_unicode_compatible
class Feature(models.Model):
name = models.CharField(max_length=20)
geom = models.GeometryField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
class MinusOneSRID(models.Model):
geom = models.PointField(srid=-1) # Minus one SRID.
diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py
index b06d6b5e1b..fbe30e8841 100644
--- a/django/contrib/gis/tests/geoapp/tests.py
+++ b/django/contrib/gis/tests/geoapp/tests.py
@@ -186,6 +186,15 @@ class GeoModelTest(TestCase):
self.assertEqual(1, qs.count())
for pc in qs: self.assertEqual(32128, pc.point.srid)
+ def test_raw_sql_query(self):
+ "Testing raw SQL query."
+ cities1 = City.objects.all()
+ # Only PostGIS would support a 'select *' query because of its recognized
+ # HEXEWKB format for geometry fields
+ cities2 = City.objects.raw('select id, name, asText(point) from geoapp_city')
+ self.assertEqual(len(cities1), len(list(cities2)))
+ self.assertTrue(isinstance(cities2[0].point, Point))
+
class GeoLookupTest(TestCase):
diff --git a/django/contrib/gis/tests/geogapp/models.py b/django/contrib/gis/tests/geogapp/models.py
index 3696ba2ff4..7e802f9321 100644
--- a/django/contrib/gis/tests/geogapp/models.py
+++ b/django/contrib/gis/tests/geogapp/models.py
@@ -1,20 +1,24 @@
from django.contrib.gis.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField(geography=True)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class Zipcode(models.Model):
code = models.CharField(max_length=10)
poly = models.PolygonField(geography=True)
objects = models.GeoManager()
- def __unicode__(self): return self.code
+ def __str__(self): return self.code
+@python_2_unicode_compatible
class County(models.Model):
name = models.CharField(max_length=25)
state = models.CharField(max_length=20)
mpoly = models.MultiPolygonField(geography=True)
objects = models.GeoManager()
- def __unicode__(self): return ' County, '.join([self.name, self.state])
+ def __str__(self): return ' County, '.join([self.name, self.state])
diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py
index aec4e15749..659fef7a93 100644
--- a/django/contrib/gis/tests/relatedapp/models.py
+++ b/django/contrib/gis/tests/relatedapp/models.py
@@ -1,37 +1,41 @@
from django.contrib.gis.db import models
from django.contrib.localflavor.us.models import USStateField
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Location(models.Model):
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.point.wkt
+ def __str__(self): return self.point.wkt
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=50)
state = USStateField()
location = models.ForeignKey(Location)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
class AugmentedLocation(Location):
extra_text = models.TextField(blank=True)
objects = models.GeoManager()
-
+
class DirectoryEntry(models.Model):
listing_text = models.CharField(max_length=50)
location = models.ForeignKey(AugmentedLocation)
objects = models.GeoManager()
+@python_2_unicode_compatible
class Parcel(models.Model):
name = models.CharField(max_length=30)
city = models.ForeignKey(City)
center1 = models.PointField()
# Throwing a curveball w/`db_column` here.
- center2 = models.PointField(srid=2276, db_column='mycenter')
+ center2 = models.PointField(srid=2276, db_column='mycenter')
border1 = models.PolygonField()
border2 = models.PolygonField(srid=2276)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
# These use the GeoManager but do not have any geographic fields.
class Author(models.Model):
diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py
index f8977059d9..4266ee4b4c 100644
--- a/django/contrib/gis/utils/ogrinspect.py
+++ b/django/contrib/gis/utils/ogrinspect.py
@@ -223,4 +223,4 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non
if name_field:
yield ''
- yield ' def __unicode__(self): return self.%s' % name_field
+ yield ' def __str__(self): return self.%s' % name_field
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index af9c842f42..18b7475ca7 100644
--- a/django/contrib/markup/templatetags/markup.py
+++ b/django/contrib/markup/templatetags/markup.py
@@ -13,7 +13,7 @@ markup syntaxes to HTML; currently there is support for:
from django import template
from django.conf import settings
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_bytes, force_text
from django.utils.safestring import mark_safe
register = template.Library()
@@ -27,7 +27,7 @@ def textile(value):
raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.")
return force_text(value)
else:
- return mark_safe(force_text(textile.textile(smart_bytes(value), encoding='utf-8', output='utf-8')))
+ return mark_safe(force_text(textile.textile(force_bytes(value), encoding='utf-8', output='utf-8')))
@register.filter(is_safe=True)
def markdown(value, arg=''):
@@ -80,5 +80,5 @@ def restructuredtext(value):
return force_text(value)
else:
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
- parts = publish_parts(source=smart_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings)
+ parts = publish_parts(source=force_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings)
return mark_safe(force_text(parts["fragment"]))
diff --git a/django/contrib/messages/storage/base.py b/django/contrib/messages/storage/base.py
index 5433bbff28..7fe8a077ed 100644
--- a/django/contrib/messages/storage/base.py
+++ b/django/contrib/messages/storage/base.py
@@ -1,14 +1,15 @@
from __future__ import unicode_literals
from django.conf import settings
-from django.utils.encoding import force_text, StrAndUnicode
+from django.utils.encoding import force_text, python_2_unicode_compatible
from django.contrib.messages import constants, utils
LEVEL_TAGS = utils.get_level_tags()
-class Message(StrAndUnicode):
+@python_2_unicode_compatible
+class Message(object):
"""
Represents an actual message that can be stored in any of the supported
storage classes (typically session- or cookie-based) and rendered in a view
@@ -35,7 +36,7 @@ class Message(StrAndUnicode):
return isinstance(other, Message) and self.level == other.level and \
self.message == other.message
- def __unicode__(self):
+ def __str__(self):
return force_text(self.message)
def _get_tags(self):
diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py
index 4233d55793..a0376b5578 100644
--- a/django/contrib/redirects/models.py
+++ b/django/contrib/redirects/models.py
@@ -1,7 +1,9 @@
from django.db import models
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Redirect(models.Model):
site = models.ForeignKey(Site)
old_path = models.CharField(_('redirect from'), max_length=200, db_index=True,
@@ -15,6 +17,6 @@ class Redirect(models.Model):
db_table = 'django_redirect'
unique_together=(('site', 'old_path'),)
ordering = ('old_path',)
-
- def __unicode__(self):
+
+ def __str__(self):
return "%s ---> %s" % (self.old_path, self.new_path)
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
index 153cde9830..c8393f23c6 100644
--- a/django/contrib/sessions/backends/base.py
+++ b/django/contrib/sessions/backends/base.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import base64
import time
from datetime import datetime, timedelta
@@ -12,6 +14,7 @@ from django.utils.crypto import constant_time_compare
from django.utils.crypto import get_random_string
from django.utils.crypto import salted_hmac
from django.utils import timezone
+from django.utils.encoding import force_bytes
class CreateError(Exception):
"""
@@ -78,15 +81,15 @@ class SessionBase(object):
"Returns the given session dictionary pickled and encoded as a string."
pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
hash = self._hash(pickled)
- return base64.encodestring(hash + ":" + pickled)
+ return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii')
def decode(self, session_data):
- encoded_data = base64.decodestring(session_data)
+ encoded_data = base64.b64decode(force_bytes(session_data))
try:
# could produce ValueError if there is no ':'
- hash, pickled = encoded_data.split(':', 1)
+ hash, pickled = encoded_data.split(b':', 1)
expected_hash = self._hash(pickled)
- if not constant_time_compare(hash, expected_hash):
+ if not constant_time_compare(hash.decode(), expected_hash):
raise SuspiciousOperation("Session data corrupted")
else:
return pickle.loads(pickled)
diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py
index 0cc17b44c3..babdb72c27 100644
--- a/django/contrib/sessions/backends/db.py
+++ b/django/contrib/sessions/backends/db.py
@@ -1,7 +1,6 @@
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation
from django.db import IntegrityError, transaction, router
-from django.utils.encoding import force_text
from django.utils import timezone
@@ -18,7 +17,7 @@ class SessionStore(SessionBase):
session_key = self.session_key,
expire_date__gt=timezone.now()
)
- return self.decode(force_text(s.session_data))
+ return self.decode(s.session_data)
except (Session.DoesNotExist, SuspiciousOperation):
self.create()
return {}
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
index 0f869088ac..20ac2c2087 100644
--- a/django/contrib/sessions/backends/file.py
+++ b/django/contrib/sessions/backends/file.py
@@ -115,7 +115,7 @@ class SessionStore(SessionBase):
renamed = False
try:
try:
- os.write(output_file_fd, self.encode(session_data))
+ os.write(output_file_fd, self.encode(session_data).encode())
finally:
os.close(output_file_fd)
os.rename(output_file_name, session_file_name)
diff --git a/django/contrib/sitemaps/tests/generic.py b/django/contrib/sitemaps/tests/generic.py
index e392cbf909..e0b0a827a6 100644
--- a/django/contrib/sitemaps/tests/generic.py
+++ b/django/contrib/sitemaps/tests/generic.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.contrib.auth.models import User
from django.test.utils import override_settings
@@ -12,8 +14,9 @@ class GenericViewsSitemapTests(SitemapTestsBase):
expected = ''
for username in User.objects.values_list("username", flat=True):
expected += "<url><loc>%s/users/%s/</loc></url>" % (self.base_url, username)
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
%s
</urlset>
-""" % expected)
+""" % expected
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
diff --git a/django/contrib/sitemaps/tests/http.py b/django/contrib/sitemaps/tests/http.py
index d71907e09a..8da971876f 100644
--- a/django/contrib/sitemaps/tests/http.py
+++ b/django/contrib/sitemaps/tests/http.py
@@ -21,11 +21,12 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_simple_sitemap_index(self):
"A simple sitemap index can be rendered"
response = self.client.get('/simple/index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
@@ -33,30 +34,34 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_simple_sitemap_custom_index(self):
"A simple sitemap index can be rendered with a custom template"
response = self.client.get('/simple/custom-index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a customised template -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
+
def test_simple_sitemap_section(self):
"A simple sitemap section can be rendered"
response = self.client.get('/simple/sitemap-simple.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
def test_simple_sitemap(self):
"A simple sitemap can be rendered"
response = self.client.get('/simple/sitemap.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
@@ -64,12 +69,13 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_simple_custom_sitemap(self):
"A simple sitemap can be rendered with a custom template"
response = self.client.get('/simple/custom-sitemap.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a customised template -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@skipUnless(settings.USE_I18N, "Internationalization is not enabled")
@override_settings(USE_L10N=True)
@@ -90,11 +96,12 @@ class HTTPSitemapTests(SitemapTestsBase):
# installed doesn't raise an exception
Site._meta.installed = False
response = self.client.get('/simple/sitemap.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>http://testserver/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % date.today())
+""" % date.today()
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@skipUnless("django.contrib.sites" in settings.INSTALLED_APPS,
"django.contrib.sites app not installed.")
@@ -131,8 +138,9 @@ class HTTPSitemapTests(SitemapTestsBase):
Check that a cached sitemap index can be rendered (#2713).
"""
response = self.client.get('/cached/index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/cached/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
diff --git a/django/contrib/sitemaps/tests/https.py b/django/contrib/sitemaps/tests/https.py
index d4f9053fc8..26241eb30b 100644
--- a/django/contrib/sitemaps/tests/https.py
+++ b/django/contrib/sitemaps/tests/https.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from datetime import date
from django.test.utils import override_settings
@@ -11,20 +13,22 @@ class HTTPSSitemapTests(SitemapTestsBase):
def test_secure_sitemap_index(self):
"A secure sitemap index can be rendered"
response = self.client.get('/secure/index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/secure/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
def test_secure_sitemap_section(self):
"A secure sitemap section can be rendered"
response = self.client.get('/secure/sitemap-simple.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@override_settings(SECURE_PROXY_SSL_HEADER=False)
@@ -34,17 +38,19 @@ class HTTPSDetectionSitemapTests(SitemapTestsBase):
def test_sitemap_index_with_https_request(self):
"A sitemap index requested in HTTPS is rendered with HTTPS links"
response = self.client.get('/simple/index.xml', **self.extra)
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url.replace('http://', 'https://'))
+""" % self.base_url.replace('http://', 'https://')
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
def test_sitemap_section_with_https_request(self):
"A sitemap section requested in HTTPS is rendered with HTTPS links"
response = self.client.get('/simple/sitemap-simple.xml', **self.extra)
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url.replace('http://', 'https://'), date.today()))
+""" % (self.base_url.replace('http://', 'https://'), date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py
index fecbff79d8..8590740658 100644
--- a/django/contrib/sites/models.py
+++ b/django/contrib/sites/models.py
@@ -1,5 +1,6 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
SITE_CACHE = {}
@@ -32,6 +33,7 @@ class SiteManager(models.Manager):
SITE_CACHE = {}
+@python_2_unicode_compatible
class Site(models.Model):
domain = models.CharField(_('domain name'), max_length=100)
@@ -44,7 +46,7 @@ class Site(models.Model):
verbose_name_plural = _('sites')
ordering = ('domain',)
- def __unicode__(self):
+ def __str__(self):
return self.domain
def save(self, *args, **kwargs):
@@ -62,6 +64,7 @@ class Site(models.Model):
pass
+@python_2_unicode_compatible
class RequestSite(object):
"""
A class that shares the primary interface of Site (i.e., it has
@@ -73,7 +76,7 @@ class RequestSite(object):
def __init__(self, request):
self.domain = self.name = request.get_host()
- def __unicode__(self):
+ def __str__(self):
return self.domain
def save(self, force_insert=False, force_update=False):
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index 7dac0ffb4c..0e16e9d649 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -192,7 +192,7 @@ Type 'yes' to continue, or 'no' to cancel: """
def clear_dir(self, path):
"""
- Deletes the given relative path using the destinatin storage backend.
+ Deletes the given relative path using the destination storage backend.
"""
dirs, files = self.storage.listdir(path)
for f in files:
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
index 2ca54dde71..9691b7849d 100644
--- a/django/contrib/staticfiles/storage.py
+++ b/django/contrib/staticfiles/storage.py
@@ -16,7 +16,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage, get_storage_class
from django.utils.datastructures import SortedDict
-from django.utils.encoding import force_text, smart_bytes
+from django.utils.encoding import force_bytes, force_text
from django.utils.functional import LazyObject
from django.utils.importlib import import_module
@@ -51,8 +51,8 @@ class CachedFilesMixin(object):
default_template = """url("%s")"""
patterns = (
("*.css", (
- br"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""",
- (br"""(@import\s*["']\s*(.*?)["'])""", """@import url("%s")"""),
+ r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""",
+ (r"""(@import\s*["']\s*(.*?)["'])""", """@import url("%s")"""),
)),
)
@@ -87,6 +87,7 @@ class CachedFilesMixin(object):
def hashed_name(self, name, content=None):
parsed_name = urlsplit(unquote(name))
clean_name = parsed_name.path.strip()
+ opened = False
if content is None:
if not self.exists(clean_name):
raise ValueError("The file '%s' could not be found with %r." %
@@ -96,9 +97,14 @@ class CachedFilesMixin(object):
except IOError:
# Handle directory paths and fragments
return name
+ opened = True
+ try:
+ file_hash = self.file_hash(clean_name, content)
+ finally:
+ if opened:
+ content.close()
path, filename = os.path.split(clean_name)
root, ext = os.path.splitext(filename)
- file_hash = self.file_hash(clean_name, content)
if file_hash is not None:
file_hash = ".%s" % file_hash
hashed_name = os.path.join(path, "%s%s%s" %
@@ -112,7 +118,7 @@ class CachedFilesMixin(object):
return urlunsplit(unparsed_name)
def cache_key(self, name):
- return 'staticfiles:%s' % hashlib.md5(smart_bytes(name)).hexdigest()
+ return 'staticfiles:%s' % hashlib.md5(force_bytes(name)).hexdigest()
def url(self, name, force=False):
"""
@@ -248,7 +254,7 @@ class CachedFilesMixin(object):
if hashed_file_exists:
self.delete(hashed_name)
# then save the processed result
- content_file = ContentFile(smart_bytes(content))
+ content_file = ContentFile(force_bytes(content))
saved_name = self._save(hashed_name, content_file)
hashed_name = force_text(saved_name.replace('\\', '/'))
processed = True
@@ -261,7 +267,7 @@ class CachedFilesMixin(object):
hashed_name = force_text(saved_name.replace('\\', '/'))
# and then set the cache accordingly
- hashed_paths[self.cache_key(name)] = hashed_name
+ hashed_paths[self.cache_key(name.replace('\\', '/'))] = hashed_name
yield name, hashed_name, processed
# Finally set the cache
diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py
index bce7ef7cfb..4815ce5567 100644
--- a/django/contrib/syndication/views.py
+++ b/django/contrib/syndication/views.py
@@ -106,7 +106,7 @@ class Feed(object):
subtitle = self.__get_dynamic_attr('subtitle', obj),
link = link,
description = self.__get_dynamic_attr('description', obj),
- language = settings.LANGUAGE_CODE.decode(),
+ language = settings.LANGUAGE_CODE,
feed_url = add_domain(
current_site.domain,
self.__get_dynamic_attr('feed_url', obj) or request.path,
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index d527e44d8b..06e8952bfb 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -1,9 +1,9 @@
"Base Cache class."
+from __future__ import unicode_literals
import warnings
from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
-from django.utils.encoding import smart_bytes
from django.utils.importlib import import_module
class InvalidCacheBackendError(ImproperlyConfigured):
@@ -23,7 +23,7 @@ def default_key_func(key, key_prefix, version):
the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
function with custom key making behavior.
"""
- return ':'.join([key_prefix, str(version), smart_bytes(key)])
+ return ':'.join([key_prefix, str(version), key])
def get_key_func(key_func):
"""
@@ -62,7 +62,7 @@ class BaseCache(object):
except (ValueError, TypeError):
self._cull_frequency = 3
- self.key_prefix = smart_bytes(params.get('KEY_PREFIX', ''))
+ self.key_prefix = params.get('KEY_PREFIX', '')
self.version = params.get('VERSION', 1)
self.key_func = get_key_func(params.get('KEY_FUNCTION', None))
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index f60b4e0cd1..348b03f733 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -12,6 +12,7 @@ from django.conf import settings
from django.core.cache.backends.base import BaseCache
from django.db import connections, router, transaction, DatabaseError
from django.utils import timezone
+from django.utils.encoding import force_bytes
class Options(object):
@@ -72,7 +73,7 @@ class DatabaseCache(BaseDatabaseCache):
transaction.commit_unless_managed(using=db)
return default
value = connections[db].ops.process_clob(row[1])
- return pickle.loads(base64.decodestring(value))
+ return pickle.loads(base64.b64decode(force_bytes(value)))
def set(self, key, value, timeout=None, version=None):
key = self.make_key(key, version=version)
@@ -103,7 +104,7 @@ class DatabaseCache(BaseDatabaseCache):
if num > self._max_entries:
self._cull(db, cursor, now)
pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
- encoded = base64.encodestring(pickled).strip()
+ encoded = base64.b64encode(pickled).strip()
cursor.execute("SELECT cache_key, expires FROM %s "
"WHERE cache_key = %%s" % table, [key])
try:
@@ -166,7 +167,7 @@ class DatabaseCache(BaseDatabaseCache):
cursor.execute("SELECT COUNT(*) FROM %s" % table)
num = cursor.fetchone()[0]
if num > self._max_entries:
- cull_num = num / self._cull_frequency
+ cull_num = num // self._cull_frequency
cursor.execute(
connections[db].ops.cache_key_culling_sql() % table,
[cull_num])
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 1170996a76..96194d458f 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -10,6 +10,7 @@ except ImportError:
import pickle
from django.core.cache.backends.base import BaseCache
+from django.utils.encoding import force_bytes
class FileBasedCache(BaseCache):
def __init__(self, dir, params):
@@ -136,7 +137,7 @@ class FileBasedCache(BaseCache):
Thus, a cache key of "foo" gets turnned into a file named
``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
"""
- path = hashlib.md5(key).hexdigest()
+ path = hashlib.md5(force_bytes(key)).hexdigest()
path = os.path.join(path[:2], path[2:4], path[4:])
return os.path.join(self._dir, path)
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index e7724f1b91..426a0a15c0 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -6,6 +6,7 @@ from threading import local
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
from django.utils import six
+from django.utils.encoding import force_str
class BaseMemcachedCache(BaseCache):
def __init__(self, server, params, library, value_not_found_exception):
@@ -50,6 +51,10 @@ class BaseMemcachedCache(BaseCache):
timeout += int(time.time())
return int(timeout)
+ def make_key(self, key, version=None):
+ # Python 2 memcache requires the key to be a byte string.
+ return force_str(super(BaseMemcachedCache, self).make_key(key, version))
+
def add(self, key, value, timeout=0, version=None):
key = self.make_key(key, version=version)
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
diff --git a/django/core/context_processors.py b/django/core/context_processors.py
index a503270cf4..ca1ac68f55 100644
--- a/django/core/context_processors.py
+++ b/django/core/context_processors.py
@@ -6,12 +6,15 @@ and returns a dictionary to add to the context.
These are referenced from the setting TEMPLATE_CONTEXT_PROCESSORS and used by
RequestContext.
"""
+from __future__ import unicode_literals
from django.conf import settings
from django.middleware.csrf import get_token
-from django.utils.encoding import smart_bytes
+from django.utils import six
+from django.utils.encoding import smart_text
from django.utils.functional import lazy
+
def csrf(request):
"""
Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
@@ -23,10 +26,10 @@ def csrf(request):
# In order to be able to provide debugging info in the
# case of misconfiguration, we use a sentinel value
# instead of returning an empty dict.
- return b'NOTPROVIDED'
+ return 'NOTPROVIDED'
else:
- return smart_bytes(token)
- _get_val = lazy(_get_val, str)
+ return smart_text(token)
+ _get_val = lazy(_get_val, six.text_type)
return {'csrf_token': _get_val() }
diff --git a/django/core/files/base.py b/django/core/files/base.py
index d0b25250a5..b81e180292 100644
--- a/django/core/files/base.py
+++ b/django/core/files/base.py
@@ -1,11 +1,14 @@
from __future__ import unicode_literals
import os
-from io import BytesIO
+from io import BytesIO, StringIO, UnsupportedOperation
-from django.utils.encoding import smart_bytes, smart_text
+from django.utils.encoding import smart_text
from django.core.files.utils import FileProxyMixin
+from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class File(FileProxyMixin):
DEFAULT_CHUNK_SIZE = 64 * 2**10
@@ -18,9 +21,6 @@ class File(FileProxyMixin):
self.mode = file.mode
def __str__(self):
- return smart_bytes(self.name or '')
-
- def __unicode__(self):
return smart_text(self.name or '')
def __repr__(self):
@@ -65,8 +65,10 @@ class File(FileProxyMixin):
if not chunk_size:
chunk_size = self.DEFAULT_CHUNK_SIZE
- if hasattr(self, 'seek'):
+ try:
self.seek(0)
+ except (AttributeError, UnsupportedOperation):
+ pass
while True:
data = self.read(chunk_size)
@@ -124,13 +126,15 @@ class File(FileProxyMixin):
def close(self):
self.file.close()
+@python_2_unicode_compatible
class ContentFile(File):
"""
A File-like object that takes just raw content, rather than an actual file.
"""
def __init__(self, content, name=None):
content = content or b''
- super(ContentFile, self).__init__(BytesIO(content), name=name)
+ stream_class = StringIO if isinstance(content, six.text_type) else BytesIO
+ super(ContentFile, self).__init__(stream_class(content), name=name)
self.size = len(content)
def __str__(self):
diff --git a/django/core/files/move.py b/django/core/files/move.py
index f9060fd3d8..3af02634fe 100644
--- a/django/core/files/move.py
+++ b/django/core/files/move.py
@@ -66,7 +66,7 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
try:
locks.lock(fd, locks.LOCK_EX)
current_chunk = None
- while current_chunk != '':
+ while current_chunk != b'':
current_chunk = old_file.read(chunk_size)
os.write(fd, current_chunk)
finally:
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 7542dcda46..0b300cd31e 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -195,11 +195,18 @@ class FileSystemStorage(Storage):
fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0))
try:
locks.lock(fd, locks.LOCK_EX)
+ _file = None
for chunk in content.chunks():
- os.write(fd, chunk)
+ if _file is None:
+ mode = 'wb' if isinstance(chunk, bytes) else 'wt'
+ _file = os.fdopen(fd, mode)
+ _file.write(chunk)
finally:
locks.unlock(fd)
- os.close(fd)
+ if _file is not None:
+ _file.close()
+ else:
+ os.close(fd)
except OSError as e:
if e.errno == errno.EEXIST:
# Ooops, the file exists. We need a new file name.
diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py
index 3a6c632975..39b99ff78f 100644
--- a/django/core/files/uploadedfile.py
+++ b/django/core/files/uploadedfile.py
@@ -8,7 +8,7 @@ from io import BytesIO
from django.conf import settings
from django.core.files.base import File
from django.core.files import temp as tempfile
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_str
__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile',
'SimpleUploadedFile')
@@ -30,7 +30,7 @@ class UploadedFile(File):
self.charset = charset
def __repr__(self):
- return smart_bytes("<%s: %s (%s)>" % (
+ return force_str("<%s: %s (%s)>" % (
self.__class__.__name__, self.name, self.content_type))
def _get_name(self):
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
index 68d540e595..c422945d6f 100644
--- a/django/core/files/uploadhandler.py
+++ b/django/core/files/uploadhandler.py
@@ -10,6 +10,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
from django.utils import importlib
+from django.utils.encoding import python_2_unicode_compatible
__all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler',
'TemporaryFileUploadHandler', 'MemoryFileUploadHandler',
@@ -21,6 +22,7 @@ class UploadFileException(Exception):
"""
pass
+@python_2_unicode_compatible
class StopUpload(UploadFileException):
"""
This exception is raised when an upload must abort.
@@ -33,7 +35,7 @@ class StopUpload(UploadFileException):
"""
self.connection_reset = connection_reset
- def __unicode__(self):
+ def __str__(self):
if self.connection_reset:
return 'StopUpload: Halt current upload.'
else:
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 5a6825f0a7..791382bac0 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import sys
+import types
from django import http
from django.core import signals
@@ -125,10 +126,10 @@ class BaseHandler(object):
# Complain if the view returned None (a common error).
if response is None:
- try:
- view_name = callback.func_name # If it's a function
- except AttributeError:
- view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+ if isinstance(callback, types.FunctionType): # FBV
+ view_name = callback.__name__
+ else: # CBV
+ view_name = callback.__class__.__name__ + '.__call__'
raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
# If the response supports deferred rendering, apply template
@@ -152,10 +153,8 @@ class BaseHandler(object):
callback, param_dict = resolver.resolve404()
response = callback(request, **param_dict)
except:
- try:
- response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
- finally:
- signals.got_request_exception.send(sender=self.__class__, request=request)
+ signals.got_request_exception.send(sender=self.__class__, request=request)
+ response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
except exceptions.PermissionDenied:
logger.warning(
'Forbidden (Permission denied): %s', request.path,
@@ -167,12 +166,10 @@ class BaseHandler(object):
callback, param_dict = resolver.resolve403()
response = callback(request, **param_dict)
except:
- try:
- response = self.handle_uncaught_exception(request,
- resolver, sys.exc_info())
- finally:
- signals.got_request_exception.send(
+ signals.got_request_exception.send(
sender=self.__class__, request=request)
+ response = self.handle_uncaught_exception(request,
+ resolver, sys.exc_info())
except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
@@ -225,7 +222,7 @@ class BaseHandler(object):
# If Http500 handler is not installed, re-raise last exception
if resolver.urlconf_module is None:
- six.reraise(exc_info[1], None, exc_info[2])
+ six.reraise(*exc_info)
# Return an HttpResponse that displays a friendly error message.
callback, param_dict = resolver.resolve500()
return callback(request, **param_dict)
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 70b23f8515..e445d07a61 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -9,7 +9,7 @@ from django.core import signals
from django.core.handlers import base
from django.core.urlresolvers import set_script_prefix
from django.utils import datastructures
-from django.utils.encoding import force_text, smart_bytes, iri_to_uri
+from django.utils.encoding import force_str, force_text, iri_to_uri
from django.utils.log import getLogger
logger = getLogger('django.request')
@@ -246,5 +246,5 @@ class WSGIHandler(base.BaseHandler):
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
- start_response(smart_bytes(status), response_headers)
+ start_response(force_str(status), response_headers)
return response
diff --git a/django/core/mail/message.py b/django/core/mail/message.py
index b332ffba04..db9023a0bb 100644
--- a/django/core/mail/message.py
+++ b/django/core/mail/message.py
@@ -99,7 +99,12 @@ def sanitize_address(addr, encoding):
if isinstance(addr, six.string_types):
addr = parseaddr(force_text(addr))
nm, addr = addr
- nm = Header(nm, encoding).encode()
+ # This try-except clause is needed on Python 3 < 3.2.4
+ # http://bugs.python.org/issue14291
+ try:
+ nm = Header(nm, encoding).encode()
+ except UnicodeEncodeError:
+ nm = Header(nm, 'utf-8').encode()
try:
addr.encode('ascii')
except UnicodeEncodeError: # IDN
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index 268cd63c38..98f75e0310 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -51,14 +51,19 @@ def find_management_module(app_name):
# module, we need look for the case where the project name is part
# of the app_name but the project directory itself isn't on the path.
try:
- f, path, descr = imp.find_module(part,path)
+ f, path, descr = imp.find_module(part, path)
except ImportError as e:
if os.path.basename(os.getcwd()) != part:
raise e
+ else:
+ if f:
+ f.close()
while parts:
part = parts.pop()
f, path, descr = imp.find_module(part, path and [path] or None)
+ if f:
+ f.close()
return path
def load_command_class(app_name, name):
diff --git a/django/core/management/base.py b/django/core/management/base.py
index 5e630d5207..895753ea12 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -12,7 +12,7 @@ import traceback
import django
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_str
from django.utils.six import StringIO
@@ -65,7 +65,7 @@ class OutputWrapper(object):
msg += ending
style_func = [f for f in (style_func, self.style_func, lambda x:x)
if f is not None][0]
- self._out.write(smart_str(style_func(msg)))
+ self._out.write(force_str(style_func(msg)))
class BaseCommand(object):
diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py
index 7c868e4b60..936d5e89ab 100644
--- a/django/core/management/commands/inspectdb.py
+++ b/django/core/management/commands/inspectdb.py
@@ -1,4 +1,7 @@
+from __future__ import unicode_literals
+
import keyword
+import re
from optparse import make_option
from django.core.management.base import NoArgsCommand, CommandError
@@ -31,6 +34,7 @@ class Command(NoArgsCommand):
table_name_filter = options.get('table_name_filter')
table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '')
+ strip_prefix = lambda s: s.startswith("u'") and s[1:] or s
cursor = connection.cursor()
yield "# This is an auto-generated Django model module."
@@ -41,6 +45,7 @@ class Command(NoArgsCommand):
yield "#"
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
yield "# into your database."
+ yield "from __future__ import unicode_literals"
yield ''
yield 'from %s import models' % self.db_module
yield ''
@@ -59,16 +64,19 @@ class Command(NoArgsCommand):
indexes = connection.introspection.get_indexes(cursor, table_name)
except NotImplementedError:
indexes = {}
+ used_column_names = [] # Holds column names used in the table so far
for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
- column_name = row[0]
- att_name = column_name.lower()
comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
extra_params = {} # Holds Field parameters such as 'db_column'.
+ column_name = row[0]
+ is_relation = i in relations
+
+ att_name, params, notes = self.normalize_col_name(
+ column_name, used_column_names, is_relation)
+ extra_params.update(params)
+ comment_notes.extend(notes)
- # If the column name can't be used verbatim as a Python
- # attribute, set the "db_column" for this Field.
- if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name:
- extra_params['db_column'] = column_name
+ used_column_names.append(att_name)
# Add primary_key and unique, if necessary.
if column_name in indexes:
@@ -77,30 +85,12 @@ class Command(NoArgsCommand):
elif indexes[column_name]['unique']:
extra_params['unique'] = True
- # Modify the field name to make it Python-compatible.
- if ' ' in att_name:
- att_name = att_name.replace(' ', '_')
- comment_notes.append('Field renamed to remove spaces.')
-
- if '-' in att_name:
- att_name = att_name.replace('-', '_')
- comment_notes.append('Field renamed to remove dashes.')
-
- if column_name != att_name:
- comment_notes.append('Field name made lowercase.')
-
- if i in relations:
+ if is_relation:
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
-
if rel_to in known_models:
field_type = 'ForeignKey(%s' % rel_to
else:
field_type = "ForeignKey('%s'" % rel_to
-
- if att_name.endswith('_id'):
- att_name = att_name[:-3]
- else:
- extra_params['db_column'] = column_name
else:
# Calling `get_field_type` to get the field type string and any
# additional paramters and notes.
@@ -110,16 +100,6 @@ class Command(NoArgsCommand):
field_type += '('
- if keyword.iskeyword(att_name):
- att_name += '_field'
- comment_notes.append('Field renamed because it was a Python reserved word.')
-
- if att_name[0].isdigit():
- att_name = 'number_%s' % att_name
- extra_params['db_column'] = six.text_type(column_name)
- comment_notes.append("Field renamed because it wasn't a "
- "valid Python identifier.")
-
# Don't output 'id = meta.AutoField(primary_key=True)', because
# that's assumed if it doesn't exist.
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
@@ -136,7 +116,9 @@ class Command(NoArgsCommand):
if extra_params:
if not field_desc.endswith('('):
field_desc += ', '
- field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
+ field_desc += ', '.join([
+ '%s=%s' % (k, strip_prefix(repr(v)))
+ for k, v in extra_params.items()])
field_desc += ')'
if comment_notes:
field_desc += ' # ' + ' '.join(comment_notes)
@@ -144,6 +126,62 @@ class Command(NoArgsCommand):
for meta_line in self.get_meta(table_name):
yield meta_line
+ def normalize_col_name(self, col_name, used_column_names, is_relation):
+ """
+ Modify the column name to make it Python-compatible as a field name
+ """
+ field_params = {}
+ field_notes = []
+
+ new_name = col_name.lower()
+ if new_name != col_name:
+ field_notes.append('Field name made lowercase.')
+
+ if is_relation:
+ if new_name.endswith('_id'):
+ new_name = new_name[:-3]
+ else:
+ field_params['db_column'] = col_name
+
+ new_name, num_repl = re.subn(r'\W', '_', new_name)
+ if num_repl > 0:
+ field_notes.append('Field renamed to remove unsuitable characters.')
+
+ if new_name.find('__') >= 0:
+ while new_name.find('__') >= 0:
+ new_name = new_name.replace('__', '_')
+ if col_name.lower().find('__') >= 0:
+ # Only add the comment if the double underscore was in the original name
+ field_notes.append("Field renamed because it contained more than one '_' in a row.")
+
+ if new_name.startswith('_'):
+ new_name = 'field%s' % new_name
+ field_notes.append("Field renamed because it started with '_'.")
+
+ if new_name.endswith('_'):
+ new_name = '%sfield' % new_name
+ field_notes.append("Field renamed because it ended with '_'.")
+
+ if keyword.iskeyword(new_name):
+ new_name += '_field'
+ field_notes.append('Field renamed because it was a Python reserved word.')
+
+ if new_name[0].isdigit():
+ new_name = 'number_%s' % new_name
+ field_notes.append("Field renamed because it wasn't a valid Python identifier.")
+
+ if new_name in used_column_names:
+ num = 0
+ while '%s_%d' % (new_name, num) in used_column_names:
+ num += 1
+ new_name = '%s_%d' % (new_name, num)
+ field_notes.append('Field renamed because of name conflict.')
+
+ if col_name != new_name and field_notes:
+ field_params['db_column'] = col_name
+
+ return new_name, field_params, field_notes
+
def get_field_type(self, connection, table_name, row):
"""
Given the database connection, the table name, and the cursor row
@@ -181,6 +219,6 @@ class Command(NoArgsCommand):
to construct the inner Meta class for the model corresponding
to the given database table name.
"""
- return [' class Meta:',
- ' db_table = %r' % table_name,
- '']
+ return [" class Meta:",
+ " db_table = '%s'" % table_name,
+ ""]
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 1896e53cee..30cf740cdf 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -196,6 +196,10 @@ class Command(BaseCommand):
loaded_object_count += loaded_objects_in_fixture
fixture_object_count += objects_in_fixture
label_found = True
+ except Exception as e:
+ if not isinstance(e, CommandError):
+ e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
+ raise
finally:
fixture.close()
@@ -209,7 +213,11 @@ class Command(BaseCommand):
# Since we disabled constraint checks, we must manually check for
# any invalid keys that might have been added
table_names = [model._meta.db_table for model in models]
- connection.check_constraints(table_names=table_names)
+ try:
+ connection.check_constraints(table_names=table_names)
+ except Exception as e:
+ e.args = ("Problem installing fixtures: %s" % e,)
+ raise
except (SystemExit, KeyboardInterrupt):
raise
@@ -217,8 +225,6 @@ class Command(BaseCommand):
if commit:
transaction.rollback(using=using)
transaction.leave_transaction_management(using=using)
- if not isinstance(e, CommandError):
- e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
raise
# If we found even one object in a fixture, we need to reset the
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index d8e8f6cff7..81c4fdf8cc 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -47,32 +47,27 @@ def _popen(cmd):
output, errors = p.communicate()
return output, errors, p.returncode
-def walk(root, topdown=True, onerror=None, followlinks=False,
- ignore_patterns=None, verbosity=0, stdout=sys.stdout):
+def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False):
"""
- A version of os.walk that can follow symlinks for Python < 2.6
+ Helper function to get all files in the given root.
"""
- if ignore_patterns is None:
- ignore_patterns = []
dir_suffix = '%s*' % os.sep
- norm_patterns = map(lambda p: p.endswith(dir_suffix)
- and p[:-len(dir_suffix)] or p, ignore_patterns)
- for dirpath, dirnames, filenames in os.walk(root, topdown, onerror):
- remove_dirs = []
- for dirname in dirnames:
+ norm_patterns = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in ignore_patterns]
+ all_files = []
+ for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=symlinks):
+ for dirname in dirnames[:]:
if is_ignored(os.path.normpath(os.path.join(dirpath, dirname)), norm_patterns):
- remove_dirs.append(dirname)
- for dirname in remove_dirs:
- dirnames.remove(dirname)
- if verbosity > 1:
- stdout.write('ignoring directory %s\n' % dirname)
- yield (dirpath, dirnames, filenames)
- if followlinks:
- for d in dirnames:
- p = os.path.join(dirpath, d)
- if os.path.islink(p):
- for link_dirpath, link_dirnames, link_filenames in walk(p):
- yield (link_dirpath, link_dirnames, link_filenames)
+ dirnames.remove(dirname)
+ if verbosity > 1:
+ stdout.write('ignoring directory %s\n' % dirname)
+ for filename in filenames:
+ if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), ignore_patterns):
+ if verbosity > 1:
+ stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
+ else:
+ all_files.extend([(dirpath, filename)])
+ all_files.sort()
+ return all_files
def is_ignored(path, ignore_patterns):
"""
@@ -83,23 +78,6 @@ def is_ignored(path, ignore_patterns):
return True
return False
-def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False):
- """
- Helper function to get all files in the given root.
- """
- all_files = []
- for (dirpath, dirnames, filenames) in walk(root, followlinks=symlinks,
- ignore_patterns=ignore_patterns, verbosity=verbosity, stdout=stdout):
- for filename in filenames:
- norm_filepath = os.path.normpath(os.path.join(dirpath, filename))
- if is_ignored(norm_filepath, ignore_patterns):
- if verbosity > 1:
- stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
- else:
- all_files.extend([(dirpath, filename)])
- all_files.sort()
- return all_files
-
def copy_plural_forms(msgs, locale, domain, verbosity, stdout=sys.stdout):
"""
Copies plural forms header contents from a Django catalog of locale to
@@ -144,7 +122,7 @@ def write_pot_file(potfile, msgs, file, work_file, is_templatized):
msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
else:
msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
- with open(potfile, 'ab') as fp:
+ with open(potfile, 'a') as fp:
fp.write(msgs)
def process_file(file, dirpath, potfile, domain, verbosity,
@@ -252,7 +230,7 @@ def write_po_file(pofile, potfile, domain, locale, verbosity, stdout,
msgs = copy_plural_forms(msgs, locale, domain, verbosity, stdout)
msgs = msgs.replace(
"#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % domain, "")
- with open(pofile, 'wb') as fp:
+ with open(pofile, 'w') as fp:
fp.write(msgs)
os.unlink(potfile)
if no_obsolete:
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index 4e7d1dbbf4..52a8cab7e4 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -80,14 +80,14 @@ class Command(NoArgsCommand):
readline.parse_and_bind("tab:complete")
# We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system
- # conventions and get $PYTHONSTARTUP first then import user.
+ # conventions and get $PYTHONSTARTUP first then .pythonrc.py.
if not use_plain:
- pythonrc = os.environ.get("PYTHONSTARTUP")
- if pythonrc and os.path.isfile(pythonrc):
- try:
- execfile(pythonrc)
- except NameError:
- pass
- # This will import .pythonrc.py as a side-effect
- import user
+ for pythonrc in (os.environ.get("PYTHONSTARTUP"),
+ os.path.expanduser('~/.pythonrc.py')):
+ if pythonrc and os.path.isfile(pythonrc):
+ try:
+ with open(pythonrc) as handle:
+ exec(compile(handle.read(), pythonrc, 'exec'))
+ except NameError:
+ pass
code.interact(local=imported_objects)
diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
index cf26e754ae..cceec07be8 100644
--- a/django/core/management/commands/syncdb.py
+++ b/django/core/management/commands/syncdb.py
@@ -75,7 +75,7 @@ class Command(NoArgsCommand):
(opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
manifest = SortedDict(
- (app_name, filter(model_installed, model_list))
+ (app_name, list(filter(model_installed, model_list)))
for app_name, model_list in all_models
)
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 7579cbe8ab..b02a548314 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import codecs
import os
import re
@@ -168,10 +169,10 @@ def custom_sql_for_model(model, style, connection):
os.path.join(app_dir, "%s.sql" % opts.object_name.lower())]
for sql_file in sql_files:
if os.path.exists(sql_file):
- with open(sql_file, 'U') as fp:
+ with codecs.open(sql_file, 'U', encoding=settings.FILE_CHARSET) as fp:
# Some backends can't execute more than one SQL statement at a time,
# so split into separate statements.
- output.extend(_split_statements(fp.read().decode(settings.FILE_CHARSET)))
+ output.extend(_split_statements(fp.read()))
return output
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index 52d0e5c89d..aa65593e9c 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -8,6 +8,8 @@ import shutil
import stat
import sys
import tempfile
+import codecs
+
try:
from urllib.request import urlretrieve
except ImportError: # Python 2
@@ -154,12 +156,12 @@ class TemplateCommand(BaseCommand):
# Only render the Python files, as we don't want to
# accidentally render Django templates files
- with open(old_path, 'r') as template_file:
+ with codecs.open(old_path, 'r', 'utf-8') as template_file:
content = template_file.read()
if filename.endswith(extensions) or filename in extra_files:
template = Template(content)
content = template.render(context)
- with open(new_path, 'w') as new_file:
+ with codecs.open(new_path, 'w', 'utf-8') as new_file:
new_file.write(content)
if self.verbosity >= 2:
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index 274f98ee79..6cd66f3a6a 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -1,7 +1,7 @@
import sys
from django.core.management.color import color_style
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_str
from django.utils.itercompat import is_iterable
from django.utils import six
@@ -13,7 +13,7 @@ class ModelErrorCollection:
def add(self, context, error):
self.errors.append((context, error))
- self.outfile.write(self.style.ERROR(smart_str("%s: %s\n" % (context, error))))
+ self.outfile.write(self.style.ERROR(force_str("%s: %s\n" % (context, error))))
def get_validation_errors(outfile, app=None):
"""
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index bdb43db9f3..276f9a4738 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -2,8 +2,6 @@
Module for abstract serializer/unserializer base classes.
"""
-from io import BytesIO
-
from django.db import models
from django.utils.encoding import smart_text
from django.utils import six
@@ -35,7 +33,7 @@ class Serializer(object):
"""
self.options = options
- self.stream = options.pop("stream", BytesIO())
+ self.stream = options.pop("stream", six.StringIO())
self.selected_fields = options.pop("fields", None)
self.use_natural_keys = options.pop("use_natural_keys", False)
@@ -125,7 +123,7 @@ class Deserializer(object):
"""
self.options = options
if isinstance(stream_or_string, six.string_types):
- self.stream = BytesIO(stream_or_string)
+ self.stream = six.StringIO(stream_or_string)
else:
self.stream = stream_or_string
# hack to make sure that the models have all been loaded before
diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py
index 3bac24d33a..ed65f2922c 100644
--- a/django/core/serializers/json.py
+++ b/django/core/serializers/json.py
@@ -12,7 +12,6 @@ import json
from django.core.serializers.base import DeserializationError
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
-from django.utils.encoding import smart_bytes
from django.utils import six
from django.utils.timezone import is_aware
@@ -61,13 +60,12 @@ def Deserializer(stream_or_string, **options):
"""
Deserialize a stream or string of JSON data.
"""
+ if not isinstance(stream_or_string, (bytes, six.string_types)):
+ stream_or_string = stream_or_string.read()
if isinstance(stream_or_string, bytes):
stream_or_string = stream_or_string.decode('utf-8')
try:
- if isinstance(stream_or_string, six.string_types):
- objects = json.loads(stream_or_string)
- else:
- objects = json.load(stream_or_string)
+ objects = json.loads(stream_or_string)
for obj in PythonDeserializer(objects, **options):
yield obj
except GeneratorExit:
diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py
index 9be1ea4492..4c11626bad 100644
--- a/django/core/serializers/pyyaml.py
+++ b/django/core/serializers/pyyaml.py
@@ -12,7 +12,6 @@ from django.db import models
from django.core.serializers.base import DeserializationError
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
-from django.utils.encoding import smart_bytes
from django.utils import six
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 2db4e5ef6a..19b287af87 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -7,6 +7,8 @@ This is a simple server for use in testing or debugging Django apps. It hasn't
been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!
"""
+from __future__ import unicode_literals
+
import os
import socket
import sys
@@ -71,12 +73,12 @@ class WSGIServerException(Exception):
class ServerHandler(simple_server.ServerHandler, object):
- error_status = "500 INTERNAL SERVER ERROR"
+ error_status = str("500 INTERNAL SERVER ERROR")
def write(self, data):
- """'write()' callable as specified by PEP 333"""
+ """'write()' callable as specified by PEP 3333"""
- assert isinstance(data, str), "write() argument must be string"
+ assert isinstance(data, bytes), "write() argument must be bytestring"
if not self.status:
raise AssertionError("write() before start_response()")
@@ -200,7 +202,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
if threading:
- httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), {})
+ httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
else:
httpd_cls = WSGIServer
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
diff --git a/django/core/signing.py b/django/core/signing.py
index 9ab8c5b8b0..147e54780c 100644
--- a/django/core/signing.py
+++ b/django/core/signing.py
@@ -32,6 +32,9 @@ start of the base64 JSON.
There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'.
These functions make use of all of them.
"""
+
+from __future__ import unicode_literals
+
import base64
import json
import time
@@ -41,7 +44,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils import baseconv
from django.utils.crypto import constant_time_compare, salted_hmac
-from django.utils.encoding import force_text, smart_bytes
+from django.utils.encoding import force_bytes, force_str, force_text
from django.utils.importlib import import_module
@@ -60,11 +63,11 @@ class SignatureExpired(BadSignature):
def b64_encode(s):
- return base64.urlsafe_b64encode(s).strip('=')
+ return base64.urlsafe_b64encode(s).strip(b'=')
def b64_decode(s):
- pad = '=' * (-len(s) % 4)
+ pad = b'=' * (-len(s) % 4)
return base64.urlsafe_b64decode(s + pad)
@@ -114,7 +117,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer,
value or re-using a salt value across different parts of your
application without good cause is a security risk.
"""
- data = serializer().dumps(obj)
+ data = force_bytes(serializer().dumps(obj))
# Flag for if it's been compressed or not
is_compressed = False
@@ -127,7 +130,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer,
is_compressed = True
base64d = b64_encode(data)
if is_compressed:
- base64d = '.' + base64d
+ base64d = b'.' + base64d
return TimestampSigner(key, salt=salt).sign(base64d)
@@ -135,35 +138,40 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma
"""
Reverse of dumps(), raises BadSignature if signature fails
"""
- base64d = smart_bytes(
- TimestampSigner(key, salt=salt).unsign(s, max_age=max_age))
+ # TimestampSigner.unsign always returns unicode but base64 and zlib
+ # compression operate on bytes.
+ base64d = force_bytes(TimestampSigner(key, salt=salt).unsign(s, max_age=max_age))
decompress = False
- if base64d[0] == '.':
+ if base64d[0] == b'.':
# It's compressed; uncompress it first
base64d = base64d[1:]
decompress = True
data = b64_decode(base64d)
if decompress:
data = zlib.decompress(data)
- return serializer().loads(data)
+ return serializer().loads(force_str(data))
class Signer(object):
+
def __init__(self, key=None, sep=':', salt=None):
- self.sep = sep
- self.key = key or settings.SECRET_KEY
- self.salt = salt or ('%s.%s' %
- (self.__class__.__module__, self.__class__.__name__))
+ # Use of native strings in all versions of Python
+ self.sep = str(sep)
+ self.key = str(key or settings.SECRET_KEY)
+ self.salt = str(salt or
+ '%s.%s' % (self.__class__.__module__, self.__class__.__name__))
def signature(self, value):
- return base64_hmac(self.salt + 'signer', value, self.key)
+ signature = base64_hmac(self.salt + 'signer', value, self.key)
+ # Convert the signature from bytes to str only on Python 3
+ return force_str(signature)
def sign(self, value):
- value = smart_bytes(value)
- return '%s%s%s' % (value, self.sep, self.signature(value))
+ value = force_str(value)
+ return str('%s%s%s') % (value, self.sep, self.signature(value))
def unsign(self, signed_value):
- signed_value = smart_bytes(signed_value)
+ signed_value = force_str(signed_value)
if not self.sep in signed_value:
raise BadSignature('No "%s" found in value' % self.sep)
value, sig = signed_value.rsplit(self.sep, 1)
@@ -178,8 +186,9 @@ class TimestampSigner(Signer):
return baseconv.base62.encode(int(time.time()))
def sign(self, value):
- value = smart_bytes('%s%s%s' % (value, self.sep, self.timestamp()))
- return '%s%s%s' % (value, self.sep, self.signature(value))
+ value = force_str(value)
+ value = str('%s%s%s') % (value, self.sep, self.timestamp())
+ return super(TimestampSigner, self).sign(value)
def unsign(self, value, max_age=None):
result = super(TimestampSigner, self).unsign(value)
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 2fe744e8eb..af3df83d0a 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -14,7 +14,7 @@ from threading import local
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import iri_to_uri, force_text, smart_bytes
+from django.utils.encoding import force_str, force_text, iri_to_uri
from django.utils.functional import memoize, lazy
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
@@ -89,18 +89,11 @@ def get_callable(lookup_view, can_fail=False):
"""
if not callable(lookup_view):
mod_name, func_name = get_mod_func(lookup_view)
+ if func_name == '':
+ return lookup_view
+
try:
- if func_name != '':
- lookup_view = getattr(import_module(mod_name), func_name)
- if not callable(lookup_view):
- raise ViewDoesNotExist(
- "Could not import %s.%s. View is not callable." %
- (mod_name, func_name))
- except AttributeError:
- if not can_fail:
- raise ViewDoesNotExist(
- "Could not import %s. View does not exist in module %s." %
- (lookup_view, mod_name))
+ mod = import_module(mod_name)
except ImportError:
parentmod, submod = get_mod_func(mod_name)
if (not can_fail and submod != '' and
@@ -110,6 +103,18 @@ def get_callable(lookup_view, can_fail=False):
(lookup_view, mod_name))
if not can_fail:
raise
+ else:
+ try:
+ lookup_view = getattr(mod, func_name)
+ if not callable(lookup_view):
+ raise ViewDoesNotExist(
+ "Could not import %s.%s. View is not callable." %
+ (mod_name, func_name))
+ except AttributeError:
+ if not can_fail:
+ raise ViewDoesNotExist(
+ "Could not import %s. View does not exist in module %s." %
+ (lookup_view, mod_name))
return lookup_view
get_callable = memoize(get_callable, _callable_cache, 1)
@@ -190,7 +195,7 @@ class RegexURLPattern(LocaleRegexProvider):
self.name = name
def __repr__(self):
- return smart_bytes('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
+ return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
def add_prefix(self, prefix):
"""
@@ -240,7 +245,14 @@ class RegexURLResolver(LocaleRegexProvider):
self._app_dict = {}
def __repr__(self):
- return smart_bytes('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
+ if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
+ # Don't bother to output the whole list, it can be huge
+ urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__
+ else:
+ urlconf_repr = repr(self.urlconf_name)
+ return force_str('<%s %s (%s:%s) %s>' % (
+ self.__class__.__name__, urlconf_repr, self.app_name,
+ self.namespace, self.regex.pattern))
def _populate(self):
lookups = MultiValueDict()
diff --git a/django/core/validators.py b/django/core/validators.py
index 456fa3cec5..317e3880bf 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -8,7 +8,7 @@ except ImportError: # Python 2
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
-from django.utils.encoding import smart_text
+from django.utils.encoding import force_text
from django.utils.ipv6 import is_valid_ipv6_address
from django.utils import six
@@ -36,7 +36,7 @@ class RegexValidator(object):
"""
Validates that the input matches the regular expression.
"""
- if not self.regex.search(smart_text(value)):
+ if not self.regex.search(force_text(value)):
raise ValidationError(self.message, code=self.code)
class URLValidator(RegexValidator):
@@ -44,7 +44,8 @@ class URLValidator(RegexValidator):
r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
r'localhost|' #localhost...
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
+ r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
@@ -54,10 +55,10 @@ class URLValidator(RegexValidator):
except ValidationError as e:
# Trivial case failed. Try for possible IDN domain
if value:
- value = smart_text(value)
+ value = force_text(value)
scheme, netloc, path, query, fragment = urlsplit(value)
try:
- netloc = netloc.encode('idna') # IDN -> ACE
+ netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
raise e
url = urlunsplit((scheme, netloc, path, query, fragment))
@@ -84,7 +85,7 @@ class EmailValidator(RegexValidator):
if value and '@' in value:
parts = value.split('@')
try:
- parts[-1] = parts[-1].encode('idna')
+ parts[-1] = parts[-1].encode('idna').decode('ascii')
except UnicodeError:
raise e
super(EmailValidator, self).__call__('@'.join(parts))
@@ -99,7 +100,7 @@ email_re = re.compile(
r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3)
validate_email = EmailValidator(email_re, _('Enter a valid e-mail address.'), 'invalid')
-slug_re = re.compile(r'^[-\w]+$')
+slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 5719f51445..efea9f802e 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -1,7 +1,7 @@
from django.db.utils import DatabaseError
try:
- import thread
+ from django.utils.six.moves import _thread as thread
except ImportError:
from django.utils.six.moves import _dummy_thread as thread
from contextlib import contextmanager
@@ -47,6 +47,8 @@ class BaseDatabaseWrapper(object):
def __ne__(self, other):
return not self == other
+ __hash__ = object.__hash__
+
def _commit(self):
if self.connection is not None:
return self.connection.commit()
@@ -621,7 +623,7 @@ class BaseDatabaseOperations(object):
exists for database backends to provide a better implementation
according to their own quoting schemes.
"""
- from django.utils.encoding import smart_text, force_text
+ from django.utils.encoding import force_text
# Convert params to contain Unicode values.
to_unicode = lambda s: force_text(s, strings_only=True, errors='replace')
@@ -630,7 +632,7 @@ class BaseDatabaseOperations(object):
else:
u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()])
- return smart_text(sql) % u_params
+ return force_text(sql) % u_params
def last_insert_id(self, cursor, table_name, pk_name):
"""
@@ -814,8 +816,8 @@ class BaseDatabaseOperations(object):
def prep_for_like_query(self, x):
"""Prepares a value for use in a LIKE query."""
- from django.utils.encoding import smart_text
- return smart_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
+ from django.utils.encoding import force_text
+ return force_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
# Same as prep_for_like_query(), but called for "iexact" matches, which
# need not necessarily be implemented using "LIKE" in the backend.
@@ -892,19 +894,21 @@ class BaseDatabaseOperations(object):
return self.year_lookup_bounds(value)
def convert_values(self, value, field):
- """Coerce the value returned by the database backend into a consistent type that
- is compatible with the field type.
+ """
+ Coerce the value returned by the database backend into a consistent type
+ that is compatible with the field type.
"""
internal_type = field.get_internal_type()
if internal_type == 'DecimalField':
return value
- elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
+ elif internal_type == 'FloatField':
+ return float(value)
+ elif (internal_type and (internal_type.endswith('IntegerField')
+ or internal_type == 'AutoField')):
return int(value)
elif internal_type in ('DateField', 'DateTimeField', 'TimeField'):
return value
- # No field, or the field isn't known to be a decimal or integer
- # Default to a float
- return float(value)
+ return value
def check_aggregate_support(self, aggregate_func):
"""Check that the backend supports the provided aggregate
@@ -1003,7 +1007,7 @@ class BaseDatabaseIntrospection(object):
for model in models.get_models(app):
if router.allow_syncdb(self.connection.alias, model):
all_models.append(model)
- tables = map(self.table_name_converter, tables)
+ tables = list(map(self.table_name_converter, tables))
return set([
m for m in all_models
if self.table_name_converter(m._meta.db_table) in tables
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index b3e09edc57..10649b64b9 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -39,7 +39,7 @@ from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation
from django.db.backends.mysql.schema import DatabaseSchemaEditor
from django.utils.functional import cached_property
-from django.utils.safestring import SafeString, SafeUnicode
+from django.utils.safestring import SafeBytes, SafeText
from django.utils import six
from django.utils import timezone
@@ -76,7 +76,7 @@ def adapt_datetime_with_timezone_support(value, conv):
# MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
# timedelta in terms of actual behavior as they are signed and include days --
# and Django expects time, so we still need to override that. We also need to
-# add special handling for SafeUnicode and SafeString as MySQLdb's type
+# add special handling for SafeText and SafeBytes as MySQLdb's type
# checking is too tight to catch those (see Django ticket #6052).
# Finally, MySQLdb always returns naive datetime objects. However, when
# timezone support is active, Django expects timezone-aware datetime objects.
@@ -403,8 +403,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
kwargs['client_flag'] = CLIENT.FOUND_ROWS
kwargs.update(settings_dict['OPTIONS'])
self.connection = Database.connect(**kwargs)
- self.connection.encoders[SafeUnicode] = self.connection.encoders[six.text_type]
- self.connection.encoders[SafeString] = self.connection.encoders[bytes]
+ self.connection.encoders[SafeText] = self.connection.encoders[six.text_type]
+ self.connection.encoders[SafeBytes] = self.connection.encoders[bytes]
connection_created.send(sender=self.__class__, connection=self)
cursor = self.connection.cursor()
if new_connection:
diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py
index bd4105ff8e..d8e9b3a202 100644
--- a/django/db/backends/mysql/compiler.py
+++ b/django/db/backends/mysql/compiler.py
@@ -1,10 +1,16 @@
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
+
from django.db.models.sql import compiler
+
class SQLCompiler(compiler.SQLCompiler):
def resolve_columns(self, row, fields=()):
values = []
- index_extra_select = len(self.query.extra_select.keys())
- for value, field in map(None, row[index_extra_select:], fields):
+ index_extra_select = len(self.query.extra_select)
+ for value, field in zip_longest(row[index_extra_select:], fields):
if (field and field.get_internal_type() in ("BooleanField", "NullBooleanField") and
value in (0, 1)):
value = bool(value)
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index 310f270a0b..1fc55115a2 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -1,8 +1,9 @@
+import re
+from .base import FIELD_TYPE
+
from django.db.backends import BaseDatabaseIntrospection
from django.utils import six
-from MySQLdb import ProgrammingError, OperationalError
-from MySQLdb.constants import FIELD_TYPE
-import re
+
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
@@ -35,9 +36,20 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
return [row[0] for row in cursor.fetchall()]
def get_table_description(self, cursor, table_name):
- "Returns a description of the table, with the DB-API cursor.description interface."
+ """
+ Returns a description of the table, with the DB-API cursor.description interface."
+ """
+ # varchar length returned by cursor.description is an internal length,
+ # not visible length (#5725), use information_schema database to fix this
+ cursor.execute("""
+ SELECT column_name, character_maximum_length FROM information_schema.columns
+ WHERE table_name = %s AND table_schema = DATABASE()
+ AND character_maximum_length IS NOT NULL""", [table_name])
+ length_map = dict(cursor.fetchall())
+
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
- return cursor.description
+ return [line[:3] + (length_map.get(line[0], line[3]),) + line[4:]
+ for line in cursor.description]
def _name_to_index(self, cursor, table_name):
"""
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 89cad12b6c..6bf2e815a7 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -10,8 +10,6 @@ import decimal
import sys
import warnings
-from django.utils import six
-
def _setup_environment(environ):
import platform
# Cygwin requires some special voodoo to set the environment variables
@@ -53,7 +51,7 @@ from django.db.backends.signals import connection_created
from django.db.backends.oracle.client import DatabaseClient
from django.db.backends.oracle.creation import DatabaseCreation
from django.db.backends.oracle.introspection import DatabaseIntrospection
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_bytes, force_text
from django.utils import six
from django.utils import timezone
@@ -66,7 +64,7 @@ IntegrityError = Database.IntegrityError
if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'):
convert_unicode = force_text
else:
- convert_unicode = smart_bytes
+ convert_unicode = force_bytes
class DatabaseFeatures(BaseDatabaseFeatures):
@@ -221,7 +219,10 @@ WHEN (new.%(col_name)s IS NULL)
def last_executed_query(self, cursor, sql, params):
# http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
# The DB API definition does not define this attribute.
- return cursor.statement.decode("utf-8")
+ if six.PY3:
+ return cursor.statement
+ else:
+ return cursor.statement.decode("utf-8")
def last_insert_id(self, cursor, table_name, pk_name):
sq_name = self._get_sequence_name(table_name)
@@ -594,10 +595,16 @@ class OracleParam(object):
param = timezone.make_aware(param, default_timezone)
param = param.astimezone(timezone.utc).replace(tzinfo=None)
+ # Oracle doesn't recognize True and False correctly in Python 3.
+ # The conversion done below works both in 2 and 3.
+ if param is True:
+ param = "1"
+ elif param is False:
+ param = "0"
if hasattr(param, 'bind_parameter'):
- self.smart_bytes = param.bind_parameter(cursor)
+ self.force_bytes = param.bind_parameter(cursor)
else:
- self.smart_bytes = convert_unicode(param, cursor.charset,
+ self.force_bytes = convert_unicode(param, cursor.charset,
strings_only)
if hasattr(param, 'input_size'):
# If parameter has `input_size` attribute, use that.
@@ -676,7 +683,7 @@ class FormatStylePlaceholderCursor(object):
self.setinputsizes(*sizes)
def _param_generator(self, params):
- return [p.smart_bytes for p in params]
+ return [p.force_bytes for p in params]
def execute(self, query, params=None):
if params is None:
diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py
index c7b10429c2..24030cdffc 100644
--- a/django/db/backends/oracle/compiler.py
+++ b/django/db/backends/oracle/compiler.py
@@ -1,4 +1,9 @@
from django.db.models.sql import compiler
+# The izip_longest was renamed to zip_longest in py3
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
class SQLCompiler(compiler.SQLCompiler):
@@ -10,10 +15,10 @@ class SQLCompiler(compiler.SQLCompiler):
rn_offset = 1
else:
rn_offset = 0
- index_start = rn_offset + len(self.query.extra_select.keys())
+ index_start = rn_offset + len(self.query.extra_select)
values = [self.query.convert_values(v, None, connection=self.connection)
for v in row[rn_offset:index_start]]
- for value, field in map(None, row[index_start:], fields):
+ for value, field in zip_longest(row[index_start:], fields):
values.append(self.query.convert_values(value, field, connection=self.connection))
return tuple(values)
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index 005c0a04b1..4cad6e06a7 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -15,7 +15,7 @@ from django.db.backends.postgresql_psycopg2.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
from django.utils.log import getLogger
-from django.utils.safestring import SafeUnicode, SafeString
+from django.utils.safestring import SafeText, SafeBytes
from django.utils import six
from django.utils.timezone import utc
@@ -30,8 +30,8 @@ DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
-psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
-psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString)
+psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString)
+psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
logger = getLogger('django.db.backends')
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index 1a08984bd2..916073c09f 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -45,8 +45,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
WHERE table_name = %s""", [table_name])
null_map = dict(cursor.fetchall())
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
- return [tuple([item for item in line[:6]] + [null_map[line[0]]=='YES'])
- for line in cursor.description]
+ return [line[:6] + (null_map[line[0]]=='YES',)
+ for line in cursor.description]
def get_relations(self, cursor, table_name):
"""
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 7918d5d3ef..d0a6fda78e 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -21,7 +21,7 @@ from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.db.backends.sqlite3.schema import DatabaseSchemaEditor
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import cached_property
-from django.utils.safestring import SafeString
+from django.utils.safestring import SafeBytes
from django.utils import six
from django.utils import timezone
@@ -57,13 +57,21 @@ def adapt_datetime_with_timezone_support(value):
value = value.astimezone(timezone.utc).replace(tzinfo=None)
return value.isoformat(str(" "))
-Database.register_converter(str("bool"), lambda s: str(s) == '1')
-Database.register_converter(str("time"), parse_time)
-Database.register_converter(str("date"), parse_date)
-Database.register_converter(str("datetime"), parse_datetime_with_timezone_support)
-Database.register_converter(str("timestamp"), parse_datetime_with_timezone_support)
-Database.register_converter(str("TIMESTAMP"), parse_datetime_with_timezone_support)
-Database.register_converter(str("decimal"), util.typecast_decimal)
+def decoder(conv_func):
+ """ The Python sqlite3 interface returns always byte strings.
+ This function converts the received value to a regular string before
+ passing it to the receiver function.
+ """
+ return lambda s: conv_func(s.decode('utf-8'))
+
+Database.register_converter(str("bool"), decoder(lambda s: s == '1'))
+Database.register_converter(str("time"), decoder(parse_time))
+Database.register_converter(str("date"), decoder(parse_date))
+Database.register_converter(str("datetime"), decoder(parse_datetime_with_timezone_support))
+Database.register_converter(str("timestamp"), decoder(parse_datetime_with_timezone_support))
+Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime_with_timezone_support))
+Database.register_converter(str("decimal"), decoder(util.typecast_decimal))
+
Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
if Database.version_info >= (2, 4, 1):
@@ -73,7 +81,7 @@ if Database.version_info >= (2, 4, 1):
# slow-down, this adapter is only registered for sqlite3 versions
# needing it (Python 2.6 and up).
Database.register_adapter(str, lambda s: s.decode('utf-8'))
- Database.register_adapter(SafeString, lambda s: s.decode('utf-8'))
+ Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8'))
class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index 8135f3548c..1df4c18c1c 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -1,6 +1,14 @@
import re
from django.db.backends import BaseDatabaseIntrospection
+field_size_re = re.compile(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$')
+
+def get_field_size(name):
+ """ Extract the size number from a "varchar(11)" type name """
+ m = field_size_re.search(name)
+ return int(m.group(1)) if m else None
+
+
# This light wrapper "fakes" a dictionary interface, because some SQLite data
# types include variables in them -- e.g. "varchar(30)" -- and can't be matched
# as a simple dictionary lookup.
@@ -32,10 +40,9 @@ class FlexibleFieldLookupDict(object):
try:
return self.base_data_types_reverse[key]
except KeyError:
- import re
- m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
- if m:
- return ('CharField', {'max_length': int(m.group(1))})
+ size = get_field_size(key)
+ if size is not None:
+ return ('CharField', {'max_length': size})
raise KeyError
class DatabaseIntrospection(BaseDatabaseIntrospection):
@@ -53,7 +60,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_table_description(self, cursor, table_name):
"Returns a description of the table, with the DB-API cursor.description interface."
- return [(info['name'], info['type'], None, None, None, None,
+ return [(info['name'], info['type'], None, info['size'], None, None,
info['null_ok']) for info in self._table_info(cursor, table_name)]
def get_relations(self, cursor, table_name):
@@ -171,6 +178,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
# cid, name, type, notnull, dflt_value, pk
return [{'name': field[1],
'type': field[2],
+ 'size': get_field_size(field[2]),
'null_ok': not field[3],
'pk': field[5] # undocumented
} for field in cursor.fetchall()]
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index 775bb3e182..75d4d07a66 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -6,6 +6,7 @@ import hashlib
from time import time
from django.conf import settings
+from django.utils.encoding import force_bytes
from django.utils.log import getLogger
from django.utils.timezone import utc
@@ -137,7 +138,7 @@ def truncate_name(name, length=None, hash_len=4):
if length is None or len(name) <= length:
return name
- hsh = hashlib.md5(name).hexdigest()[:hash_len]
+ hsh = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len]
return '%s%s' % (name[:length-hash_len], hsh)
def format_number(value, max_digits, decimal_places):
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 569b8e876c..62024c8ee4 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -23,7 +23,7 @@ from django.db.models import signals
from django.db.models.loading import register_models, get_model
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import curry
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_str, force_text
from django.utils import six
from django.utils.text import get_text_list, capfirst
@@ -404,10 +404,10 @@ class Model(six.with_metaclass(ModelBase, object)):
u = six.text_type(self)
except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]'
- return smart_bytes('<%s: %s>' % (self.__class__.__name__, u))
+ return force_str('<%s: %s>' % (self.__class__.__name__, u))
def __str__(self):
- if hasattr(self, '__unicode__'):
+ if not six.PY3 and hasattr(self, '__unicode__'):
return force_text(self).encode('utf-8')
return '%s object' % self.__class__.__name__
@@ -475,6 +475,7 @@ class Model(six.with_metaclass(ModelBase, object)):
that the "save" must be an SQL insert or update (or equivalent for
non-SQL backends), respectively. Normally, they should not be set.
"""
+ using = using or router.db_for_write(self.__class__, instance=self)
if force_insert and (force_update or update_fields):
raise ValueError("Cannot force both insert and updating in model saving.")
@@ -502,6 +503,23 @@ class Model(six.with_metaclass(ModelBase, object)):
"model or are m2m fields: %s"
% ', '.join(non_model_fields))
+ # If saving to the same database, and this model is deferred, then
+ # automatically do a "update_fields" save on the loaded fields.
+ elif not force_insert and self._deferred and using == self._state.db:
+ field_names = set()
+ for field in self._meta.fields:
+ if not field.primary_key and not hasattr(field, 'through'):
+ field_names.add(field.attname)
+ deferred_fields = [
+ f.attname for f in self._meta.fields
+ if f.attname not in self.__dict__
+ and isinstance(self.__class__.__dict__[f.attname],
+ DeferredAttribute)]
+
+ loaded_fields = field_names.difference(deferred_fields)
+ if loaded_fields:
+ update_fields = frozenset(loaded_fields)
+
self.save_base(using=using, force_insert=force_insert,
force_update=force_update, update_fields=update_fields)
save.alters_data = True
@@ -636,7 +654,7 @@ class Model(six.with_metaclass(ModelBase, object)):
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = is_next and 'gt' or 'lt'
order = not is_next and '-' or ''
- param = smart_bytes(getattr(self, field.attname))
+ param = force_text(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
@@ -761,7 +779,7 @@ class Model(six.with_metaclass(ModelBase, object)):
lookup_kwargs[str(field_name)] = lookup_value
# some fields were skipped, no reason to do the check
- if len(unique_check) != len(lookup_kwargs.keys()):
+ if len(unique_check) != len(lookup_kwargs):
continue
qs = model_class._default_manager.filter(**lookup_kwargs)
@@ -837,7 +855,7 @@ class Model(six.with_metaclass(ModelBase, object)):
}
# unique_together
else:
- field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check)
+ field_labels = [capfirst(opts.get_field(f).verbose_name) for f in unique_check]
field_labels = get_text_list(field_labels, _('and'))
return _("%(model_name)s with this %(field_label)s already exists.") % {
'model_name': six.text_type(model_name),
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index a71f4a33ef..639ef6ee10 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -58,8 +58,9 @@ class ExpressionNode(tree.Node):
def __mul__(self, other):
return self._combine(other, self.MUL, False)
- def __div__(self, other):
+ def __truediv__(self, other):
return self._combine(other, self.DIV, False)
+ __div__ = __truediv__ # Python 2 compatibility
def __mod__(self, other):
return self._combine(other, self.MOD, False)
@@ -79,8 +80,9 @@ class ExpressionNode(tree.Node):
def __rmul__(self, other):
return self._combine(other, self.MUL, True)
- def __rdiv__(self, other):
+ def __rtruediv__(self, other):
return self._combine(other, self.DIV, True)
+ __rdiv__ = __rtruediv__ # Python 2 compatibility
def __rmod__(self, other):
return self._combine(other, self.MOD, True)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index d07851bbf5..58ae3413f3 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -1047,13 +1047,14 @@ class GenericIPAddressField(Field):
description = _("IP address")
default_error_messages = {}
- def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs):
+ def __init__(self, verbose_name=None, name=None, protocol='both',
+ unpack_ipv4=False, *args, **kwargs):
self.unpack_ipv4 = unpack_ipv4
self.default_validators, invalid_error_message = \
validators.ip_address_validators(protocol, unpack_ipv4)
self.default_error_messages['invalid'] = invalid_error_message
kwargs['max_length'] = 39
- Field.__init__(self, *args, **kwargs)
+ Field.__init__(self, verbose_name, name, *args, **kwargs)
def get_internal_type(self):
return "GenericIPAddressField"
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
index ad4c36ca0d..f16632ef25 100644
--- a/django/db/models/fields/files.py
+++ b/django/db/models/fields/files.py
@@ -8,7 +8,7 @@ from django.core.files.base import File
from django.core.files.storage import default_storage
from django.core.files.images import ImageFile
from django.db.models import signals
-from django.utils.encoding import force_text, smart_bytes
+from django.utils.encoding import force_str, force_text
from django.utils import six
from django.utils.translation import ugettext_lazy as _
@@ -280,7 +280,7 @@ class FileField(Field):
setattr(cls, self.name, self.descriptor_class(self))
def get_directory_name(self):
- return os.path.normpath(force_text(datetime.datetime.now().strftime(smart_bytes(self.upload_to))))
+ return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(self.upload_to))))
def get_filename(self, filename):
return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py
index d4d7d75222..e6153aefe0 100644
--- a/django/db/models/fields/subclassing.py
+++ b/django/db/models/fields/subclassing.py
@@ -2,8 +2,9 @@
Convenience routines for creating non-trivial Field subclasses, as well as
backwards compatibility utilities.
-Add SubfieldBase as the __metaclass__ for your Field subclass, implement
-to_python() and the other necessary methods and everything will work seamlessly.
+Add SubfieldBase as the metaclass for your Field subclass, implement
+to_python() and the other necessary methods and everything will work
+seamlessly.
"""
class SubfieldBase(type):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 2e8ccb49ce..6814ce27ff 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import re
from bisect import bisect
@@ -8,9 +10,10 @@ from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.proxy import OrderWrt
from django.db.models.loading import get_models, app_cache_ready
from django.utils.translation import activate, deactivate_all, get_language, string_concat
-from django.utils.encoding import force_text, smart_bytes
+from django.utils.encoding import force_text, smart_text
from django.utils.datastructures import SortedDict
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
@@ -20,6 +23,7 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract', 'managed', 'proxy', 'auto_created')
+@python_2_unicode_compatible
class Options(object):
def __init__(self, meta, app_label=None):
self.local_fields, self.local_many_to_many = [], []
@@ -199,7 +203,7 @@ class Options(object):
return '<Options for %s>' % self.object_name
def __str__(self):
- return "%s.%s" % (smart_bytes(self.app_label), smart_bytes(self.module_name))
+ return "%s.%s" % (smart_text(self.app_label), smart_text(self.module_name))
def verbose_name_raw(self):
"""
@@ -383,7 +387,7 @@ class Options(object):
predicates.append(lambda k, v: not k.field.rel.is_hidden())
cache = (self._related_objects_proxy_cache if include_proxy_eq
else self._related_objects_cache)
- return filter(lambda t: all([p(*t) for p in predicates]), cache.items())
+ return [t for t in cache.items() if all(p(*t) for p in predicates)]
def _fill_related_objects_cache(self):
cache = SortedDict()
diff --git a/django/db/models/query.py b/django/db/models/query.py
index e8d6ae2a7b..05c049b31f 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -472,7 +472,7 @@ class QuerySet(object):
return self.get(**lookup), False
except self.model.DoesNotExist:
# Re-raise the IntegrityError with its original traceback.
- six.reraise(exc_info[1], None, exc_info[2])
+ six.reraise(*exc_info)
def latest(self, field_name=None):
"""
@@ -498,9 +498,7 @@ class QuerySet(object):
"Cannot use 'limit' or 'offset' with in_bulk"
if not id_list:
return {}
- qs = self._clone()
- qs.query.add_filter(('pk__in', id_list))
- qs.query.clear_ordering(force_empty=True)
+ qs = self.filter(pk__in=id_list).order_by()
return dict([(obj._get_pk_val(), obj) for obj in qs])
def delete(self):
@@ -1107,7 +1105,7 @@ class ValuesListQuerySet(ValuesQuerySet):
# If a field list has been specified, use it. Otherwise, use the
# full list of fields, including extras and aggregates.
if self._fields:
- fields = list(self._fields) + filter(lambda f: f not in self._fields, aggregate_names)
+ fields = list(self._fields) + [f for f in aggregate_names if f not in self._fields]
else:
fields = names
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 1311ea546c..cf7cad29bd 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -470,9 +470,7 @@ class SQLCompiler(object):
# Must use left outer joins for nullable fields and their relations.
# Ordering or distinct must not affect the returned set, and INNER
# JOINS for nullable fields could do this.
- if joins_to_promote:
- self.query.promote_alias_chain(joins_to_promote,
- self.query.alias_map[joins_to_promote[0]].join_type == self.query.LOUTER)
+ self.query.promote_joins(joins_to_promote)
return field, col, alias, joins, opts
def _final_join_removal(self, col, alias):
@@ -645,8 +643,6 @@ class SQLCompiler(object):
alias_chain.append(alias)
for (dupe_opts, dupe_col) in dupe_set:
self.query.update_dupe_avoidance(dupe_opts, dupe_col, alias)
- if self.query.alias_map[root_alias].join_type == self.query.LOUTER:
- self.query.promote_alias_chain(alias_chain, True)
else:
alias = root_alias
@@ -663,8 +659,6 @@ class SQLCompiler(object):
columns, aliases = self.get_default_columns(start_alias=alias,
opts=f.rel.to._meta, as_pairs=True)
self.query.related_select_cols.extend(columns)
- if self.query.alias_map[alias].join_type == self.query.LOUTER:
- self.query.promote_alias_chain(aliases, True)
self.query.related_select_fields.extend(f.rel.to._meta.fields)
if restricted:
next = requested.get(f.name, {})
@@ -738,7 +732,9 @@ class SQLCompiler(object):
self.query.related_select_fields.extend(model._meta.fields)
next = requested.get(f.related_query_name(), {})
- new_nullable = f.null or None
+ # Use True here because we are looking at the _reverse_ side of
+ # the relation, which is always nullable.
+ new_nullable = True
self.fill_related_selections(model._meta, table, cur_depth+1,
used, next, restricted, new_nullable)
@@ -786,7 +782,7 @@ class SQLCompiler(object):
row = self.resolve_columns(row, fields)
if has_aggregate_select:
- aggregate_start = len(self.query.extra_select.keys()) + len(self.query.select)
+ aggregate_start = len(self.query.extra_select) + len(self.query.select)
aggregate_end = aggregate_start + len(self.query.aggregate_select)
row = tuple(row[:aggregate_start]) + tuple([
self.query.resolve_aggregate(value, aggregate, self.connection)
diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py
index 612755a012..b9cf2c96fd 100644
--- a/django/db/models/sql/constants.py
+++ b/django/db/models/sql/constants.py
@@ -1,7 +1,7 @@
from collections import namedtuple
import re
-# Valid query types (a dictionary is used for speedy lookups).
+# Valid query types (a set is used for speedy lookups).
QUERY_TERMS = set([
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index be257a5410..1915efa7b9 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -505,7 +505,7 @@ class Query(object):
# Again, some of the tables won't have aliases due to
# the trimming of unnecessary tables.
if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias):
- self.promote_alias(alias, True)
+ self.promote_joins([alias], True)
# Now relabel a copy of the rhs where-clause and add it to the current
# one.
@@ -682,32 +682,38 @@ class Query(object):
""" Decreases the reference count for this alias. """
self.alias_refcount[alias] -= amount
- def promote_alias(self, alias, unconditional=False):
+ def promote_joins(self, aliases, unconditional=False):
"""
- Promotes the join type of an alias to an outer join if it's possible
- for the join to contain NULL values on the left. If 'unconditional' is
- False, the join is only promoted if it is nullable, otherwise it is
- always promoted.
+ Promotes recursively the join type of given aliases and its children to
+ an outer join. If 'unconditional' is False, the join is only promoted if
+ it is nullable or the parent join is an outer join.
- Returns True if the join was promoted by this call.
+ Note about join promotion: When promoting any alias, we make sure all
+ joins which start from that alias are promoted, too. When adding a join
+ in join(), we make sure any join added to already existing LOUTER join
+ is generated as LOUTER. This ensures we don't ever have broken join
+ chains which contain first a LOUTER join, then an INNER JOIN, that is
+ this kind of join should never be generated: a LOUTER b INNER c. The
+ reason for avoiding this type of join chain is that the INNER after
+ the LOUTER will effectively remove any effect the LOUTER had.
"""
- if ((unconditional or self.alias_map[alias].nullable) and
- self.alias_map[alias].join_type != self.LOUTER):
- data = self.alias_map[alias]
- data = data._replace(join_type=self.LOUTER)
- self.alias_map[alias] = data
- return True
- return False
-
- def promote_alias_chain(self, chain, must_promote=False):
- """
- Walks along a chain of aliases, promoting the first nullable join and
- any joins following that. If 'must_promote' is True, all the aliases in
- the chain are promoted.
- """
- for alias in chain:
- if self.promote_alias(alias, must_promote):
- must_promote = True
+ aliases = list(aliases)
+ while aliases:
+ alias = aliases.pop(0)
+ parent_alias = self.alias_map[alias].lhs_alias
+ parent_louter = (parent_alias
+ and self.alias_map[parent_alias].join_type == self.LOUTER)
+ already_louter = self.alias_map[alias].join_type == self.LOUTER
+ if ((unconditional or self.alias_map[alias].nullable
+ or parent_louter) and not already_louter):
+ data = self.alias_map[alias]._replace(join_type=self.LOUTER)
+ self.alias_map[alias] = data
+ # Join type of 'alias' changed, so re-examine all aliases that
+ # refer to this one.
+ aliases.extend(
+ join for join in self.alias_map.keys()
+ if (self.alias_map[join].lhs_alias == alias
+ and join not in aliases))
def reset_refcounts(self, to_counts):
"""
@@ -726,19 +732,10 @@ class Query(object):
then and which ones haven't been used and promotes all of those
aliases, plus any children of theirs in the alias tree, to outer joins.
"""
- # FIXME: There's some (a lot of!) overlap with the similar OR promotion
- # in add_filter(). It's not quite identical, but is very similar. So
- # pulling out the common bits is something for later.
- considered = {}
for alias in self.tables:
- if alias not in used_aliases:
- continue
- if (alias not in initial_refcounts or
+ if alias in used_aliases and (alias not in initial_refcounts or
self.alias_refcount[alias] == initial_refcounts[alias]):
- parent = self.alias_map[alias].lhs_alias
- must_promote = considered.get(parent, False)
- promoted = self.promote_alias(alias, must_promote)
- considered[alias] = must_promote or promoted
+ self.promote_joins([alias])
def change_aliases(self, change_map):
"""
@@ -875,6 +872,9 @@ class Query(object):
LOUTER join type. This is used when joining certain types of querysets
and Q-objects together.
+ A join is always created as LOUTER if the lhs alias is LOUTER to make
+ sure we do not generate chains like a LOUTER b INNER c.
+
If 'nullable' is True, the join can potentially involve NULL values and
is a candidate for promotion (to "left outer") when combining querysets.
"""
@@ -900,8 +900,8 @@ class Query(object):
if self.alias_map[alias].lhs_alias != lhs:
continue
self.ref_alias(alias)
- if promote:
- self.promote_alias(alias)
+ if promote or (lhs and self.alias_map[lhs].join_type == self.LOUTER):
+ self.promote_joins([alias])
return alias
# No reuse is possible, so we need a new alias.
@@ -910,7 +910,12 @@ class Query(object):
# Not all tables need to be joined to anything. No join type
# means the later columns are ignored.
join_type = None
- elif promote or outer_if_first:
+ elif (promote or outer_if_first
+ or self.alias_map[lhs].join_type == self.LOUTER):
+ # We need to use LOUTER join if asked by promote or outer_if_first,
+ # or if the LHS table is left-joined in the query. Adding inner join
+ # to an existing outer join effectively cancels the effect of the
+ # outer join.
join_type = self.LOUTER
else:
join_type = self.INNER
@@ -1004,8 +1009,7 @@ class Query(object):
# If the aggregate references a model or field that requires a join,
# those joins must be LEFT OUTER - empty join rows must be returned
# in order for zeros to be returned for those aggregates.
- for column_alias in join_list:
- self.promote_alias(column_alias, unconditional=True)
+ self.promote_joins(join_list, True)
col = (join_list[-1], col)
else:
@@ -1124,7 +1128,7 @@ class Query(object):
# If the comparison is against NULL, we may need to use some left
# outer joins when creating the join chain. This is only done when
# needed, as it's less efficient at the database level.
- self.promote_alias_chain(join_list)
+ self.promote_joins(join_list)
join_promote = True
# Process the join list to see if we can remove any inner joins from
@@ -1155,16 +1159,16 @@ class Query(object):
# This means that we are dealing with two different query
# subtrees, so we don't need to do any join promotion.
continue
- join_promote = join_promote or self.promote_alias(join, unconditional)
+ join_promote = join_promote or self.promote_joins([join], unconditional)
if table != join:
- table_promote = self.promote_alias(table)
+ table_promote = self.promote_joins([table])
# We only get here if we have found a table that exists
# in the join list, but isn't on the original tables list.
# This means we've reached the point where we only have
# new tables, so we can break out of this promotion loop.
break
- self.promote_alias_chain(join_it, join_promote)
- self.promote_alias_chain(table_it, table_promote or join_promote)
+ self.promote_joins(join_it, join_promote)
+ self.promote_joins(table_it, table_promote or join_promote)
if having_clause or force_having:
if (alias, col) not in self.group_by:
@@ -1176,7 +1180,7 @@ class Query(object):
connector)
if negate:
- self.promote_alias_chain(join_list)
+ self.promote_joins(join_list)
if lookup_type != 'isnull':
if len(join_list) > 1:
for alias in join_list:
@@ -1550,7 +1554,7 @@ class Query(object):
N-to-many relation field.
"""
query = Query(self.model)
- query.add_filter(filter_expr, can_reuse=can_reuse)
+ query.add_filter(filter_expr)
query.bump_prefix()
query.clear_ordering(True)
query.set_start(prefix)
@@ -1650,7 +1654,7 @@ class Query(object):
final_alias = join.lhs_alias
col = join.lhs_join_col
joins = joins[:-1]
- self.promote_alias_chain(joins[1:])
+ self.promote_joins(joins[1:])
self.select.append((final_alias, col))
self.select_fields.append(field)
except MultiJoin:
diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py
index d8728e219a..84d1b2183c 100644
--- a/django/dispatch/saferef.py
+++ b/django/dispatch/saferef.py
@@ -149,9 +149,11 @@ class BoundMethodWeakref(object):
self.selfName,
self.funcName,
)
-
+
__repr__ = __str__
-
+
+ __hash__ = object.__hash__
+
def __bool__( self ):
"""Whether we are still a valid reference"""
return self() is not None
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 45b758202a..3299c2becc 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -12,7 +12,7 @@ from django.forms.util import flatatt, ErrorDict, ErrorList
from django.forms.widgets import Media, media_property, TextInput, Textarea
from django.utils.datastructures import SortedDict
from django.utils.html import conditional_escape, format_html
-from django.utils.encoding import StrAndUnicode, smart_text, force_text
+from django.utils.encoding import smart_text, force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import six
@@ -68,7 +68,8 @@ class DeclarativeFieldsMetaclass(type):
new_class.media = media_property(new_class)
return new_class
-class BaseForm(StrAndUnicode):
+@python_2_unicode_compatible
+class BaseForm(object):
# This is the main implementation of all the Form logic. Note that this
# class is different than Form. See the comments by the Form class for more
# information. Any improvements to the form API should be made to *this*
@@ -95,7 +96,7 @@ class BaseForm(StrAndUnicode):
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
- def __unicode__(self):
+ def __str__(self):
return self.as_table()
def __iter__(self):
@@ -387,7 +388,8 @@ class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
# to define a form using declarative syntax.
# BaseForm itself has no way of designating self.fields.
-class BoundField(StrAndUnicode):
+@python_2_unicode_compatible
+class BoundField(object):
"A Field plus data"
def __init__(self, form, field, name):
self.form = form
@@ -402,7 +404,7 @@ class BoundField(StrAndUnicode):
self.label = self.field.label
self.help_text = field.help_text or ''
- def __unicode__(self):
+ def __str__(self):
"""Renders this field as an HTML widget."""
if self.field.show_hidden_initial:
return self.as_widget() + self.as_hidden(only_initial=True)
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 4ea8dc4ca9..bdcef92dec 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -5,7 +5,7 @@ from django.forms import Form
from django.forms.fields import IntegerField, BooleanField
from django.forms.util import ErrorList
from django.forms.widgets import Media, HiddenInput
-from django.utils.encoding import StrAndUnicode
+from django.utils.encoding import python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.six.moves import xrange
@@ -33,7 +33,8 @@ class ManagementForm(Form):
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
-class BaseFormSet(StrAndUnicode):
+@python_2_unicode_compatible
+class BaseFormSet(object):
"""
A collection of instances of the same Form class.
"""
@@ -51,7 +52,7 @@ class BaseFormSet(StrAndUnicode):
# construct the forms in the formset
self._construct_forms()
- def __unicode__(self):
+ def __str__(self):
return self.as_table()
def __iter__(self):
@@ -94,10 +95,11 @@ class BaseFormSet(StrAndUnicode):
total_forms = initial_forms + self.extra
# Allow all existing related objects/inlines to be displayed,
# but don't allow extra beyond max_num.
- if initial_forms > self.max_num >= 0:
- total_forms = initial_forms
- elif total_forms > self.max_num >= 0:
- total_forms = self.max_num
+ if self.max_num is not None:
+ if initial_forms > self.max_num >= 0:
+ total_forms = initial_forms
+ elif total_forms > self.max_num >= 0:
+ total_forms = self.max_num
return total_forms
def initial_form_count(self):
@@ -107,7 +109,7 @@ class BaseFormSet(StrAndUnicode):
else:
# Use the length of the inital data if it's there, 0 otherwise.
initial_forms = self.initial and len(self.initial) or 0
- if initial_forms > self.max_num >= 0:
+ if self.max_num is not None and (initial_forms > self.max_num >= 0):
initial_forms = self.max_num
return initial_forms
@@ -252,13 +254,10 @@ class BaseFormSet(StrAndUnicode):
errors = property(_get_errors)
def _should_delete_form(self, form):
- # The way we lookup the value of the deletion field here takes
- # more code than we'd like, but the form's cleaned_data will
- # not exist if the form is invalid.
- field = form.fields[DELETION_FIELD_NAME]
- raw_value = form._raw_value(DELETION_FIELD_NAME)
- should_delete = field.clean(raw_value)
- return should_delete
+ """
+ Returns whether or not the form was marked for deletion.
+ """
+ return form.cleaned_data.get(DELETION_FIELD_NAME, False)
def is_valid(self):
"""
diff --git a/django/forms/models.py b/django/forms/models.py
index 0b07d31d9a..1aa49eaaec 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -18,7 +18,6 @@ from django.utils.datastructures import SortedDict
from django.utils import six
from django.utils.text import get_text_list, capfirst
from django.utils.translation import ugettext_lazy as _, ugettext
-from django.utils import six
__all__ = (
@@ -401,13 +400,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
'formfield_callback': formfield_callback
}
- form_metaclass = ModelFormMetaclass
-
- # TODO: this doesn't work under Python 3.
- if issubclass(form, BaseModelForm) and hasattr(form, '__metaclass__'):
- form_metaclass = form.__metaclass__
-
- return form_metaclass(class_name, (form,), form_class_attrs)
+ # Instatiate type(form) in order to use the same metaclass as form.
+ return type(form)(class_name, (form,), form_class_attrs)
# ModelFormSets ##############################################################
@@ -597,6 +591,10 @@ class BaseModelFormSet(BaseFormSet):
return []
saved_instances = []
+ try:
+ forms_to_delete = self.deleted_forms
+ except AttributeError:
+ forms_to_delete = []
for form in self.initial_forms:
pk_name = self._pk_field.name
raw_pk_value = form._raw_value(pk_name)
@@ -607,7 +605,7 @@ class BaseModelFormSet(BaseFormSet):
pk_value = getattr(pk_value, 'pk', pk_value)
obj = self._existing_object(pk_value)
- if self.can_delete and self._should_delete_form(form):
+ if form in forms_to_delete:
self.deleted_objects.append(obj)
obj.delete()
continue
diff --git a/django/forms/util.py b/django/forms/util.py
index cd6b52df6f..9b1bcebe33 100644
--- a/django/forms/util.py
+++ b/django/forms/util.py
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf import settings
from django.utils.html import format_html, format_html_join
-from django.utils.encoding import StrAndUnicode, force_text
+from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@@ -22,13 +22,14 @@ def flatatt(attrs):
"""
return format_html_join('', ' {0}="{1}"', attrs.items())
-class ErrorDict(dict, StrAndUnicode):
+@python_2_unicode_compatible
+class ErrorDict(dict):
"""
A collection of errors that knows how to display itself in various formats.
The dictionary keys are the field names, and the values are the errors.
"""
- def __unicode__(self):
+ def __str__(self):
return self.as_ul()
def as_ul(self):
@@ -42,11 +43,12 @@ class ErrorDict(dict, StrAndUnicode):
def as_text(self):
return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_text(i) for i in v])) for k, v in self.items()])
-class ErrorList(list, StrAndUnicode):
+@python_2_unicode_compatible
+class ErrorList(list):
"""
A collection of errors that knows how to display itself in various formats.
"""
- def __unicode__(self):
+ def __str__(self):
return self.as_ul()
def as_ul(self):
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index be9ac8eb8f..fdb9f50688 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -17,11 +17,9 @@ from django.forms.util import flatatt, to_current_timezone
from django.utils.datastructures import MultiValueDict, MergeDict
from django.utils.html import conditional_escape, format_html, format_html_join
from django.utils.translation import ugettext, ugettext_lazy
-from django.utils.encoding import StrAndUnicode, force_text
+from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
-from django.utils import six
-from django.utils import datetime_safe, formats
-from django.utils import six
+from django.utils import datetime_safe, formats, six
__all__ = (
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
@@ -34,7 +32,8 @@ __all__ = (
MEDIA_TYPES = ('css','js')
-class Media(StrAndUnicode):
+@python_2_unicode_compatible
+class Media(object):
def __init__(self, media=None, **kwargs):
if media:
media_attrs = media.__dict__
@@ -51,7 +50,7 @@ class Media(StrAndUnicode):
# if media_attrs != {}:
# raise TypeError("'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys()))
- def __unicode__(self):
+ def __str__(self):
return self.render()
def render(self):
@@ -142,7 +141,8 @@ class MediaDefiningClass(type):
new_class.media = media_property(new_class)
return new_class
-class SubWidget(StrAndUnicode):
+@python_2_unicode_compatible
+class SubWidget(object):
"""
Some widgets are made of multiple HTML elements -- namely, RadioSelect.
This is a class that represents the "inner" HTML element of a widget.
@@ -152,7 +152,7 @@ class SubWidget(StrAndUnicode):
self.name, self.value = name, value
self.attrs, self.choices = attrs, choices
- def __unicode__(self):
+ def __str__(self):
args = [self.name, self.value, self.attrs]
if self.choices:
args.append(self.choices)
@@ -647,6 +647,7 @@ class SelectMultiple(Select):
data_set = set([force_text(value) for value in data])
return data_set != initial_set
+@python_2_unicode_compatible
class RadioInput(SubWidget):
"""
An object used by RadioFieldRenderer that represents a single
@@ -660,7 +661,7 @@ class RadioInput(SubWidget):
self.choice_label = force_text(choice[1])
self.index = index
- def __unicode__(self):
+ def __str__(self):
return self.render()
def render(self, name=None, value=None, attrs=None, choices=()):
@@ -685,7 +686,8 @@ class RadioInput(SubWidget):
final_attrs['checked'] = 'checked'
return format_html('<input{0} />', flatatt(final_attrs))
-class RadioFieldRenderer(StrAndUnicode):
+@python_2_unicode_compatible
+class RadioFieldRenderer(object):
"""
An object used by RadioSelect to enable customization of radio widgets.
"""
@@ -702,7 +704,7 @@ class RadioFieldRenderer(StrAndUnicode):
choice = self.choices[idx] # Let the IndexError propogate
return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
- def __unicode__(self):
+ def __str__(self):
return self.render()
def render(self):
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 05824ad1d9..2198f38cbb 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -61,14 +61,14 @@ else:
if not _cookie_allows_colon_in_names:
def load(self, rawdata):
self.bad_cookies = set()
- super(SimpleCookie, self).load(smart_bytes(rawdata))
+ super(SimpleCookie, self).load(force_str(rawdata))
for key in self.bad_cookies:
del self[key]
# override private __set() method:
# (needed for using our Morsel, and for laxness with CookieError
def _BaseCookie__set(self, key, real_value, coded_value):
- key = smart_bytes(key)
+ key = force_str(key)
try:
M = self.get(key, Morsel())
M.set(key, real_value, coded_value)
@@ -85,7 +85,7 @@ from django.core.files import uploadhandler
from django.http.multipartparser import MultiPartParser
from django.http.utils import *
from django.utils.datastructures import MultiValueDict, ImmutableList
-from django.utils.encoding import smart_bytes, iri_to_uri, force_text
+from django.utils.encoding import force_bytes, force_str, force_text, iri_to_uri
from django.utils.http import cookie_date
from django.utils import six
from django.utils import timezone
@@ -113,7 +113,7 @@ def build_request_repr(request, path_override=None, GET_override=None,
get = (pformat(GET_override)
if GET_override is not None
else pformat(request.GET))
- except:
+ except Exception:
get = '<could not parse>'
if request._post_parse_error:
post = '<could not parse>'
@@ -122,22 +122,22 @@ def build_request_repr(request, path_override=None, GET_override=None,
post = (pformat(POST_override)
if POST_override is not None
else pformat(request.POST))
- except:
+ except Exception:
post = '<could not parse>'
try:
cookies = (pformat(COOKIES_override)
if COOKIES_override is not None
else pformat(request.COOKIES))
- except:
+ except Exception:
cookies = '<could not parse>'
try:
meta = (pformat(META_override)
if META_override is not None
else pformat(request.META))
- except:
+ except Exception:
meta = '<could not parse>'
path = path_override if path_override is not None else request.path
- return smart_bytes('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' %
+ return force_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' %
(request.__class__.__name__,
path,
six.text_type(get),
@@ -177,14 +177,14 @@ class HttpRequest(object):
# Reconstruct the host using the algorithm from PEP 333.
host = self.META['SERVER_NAME']
server_port = str(self.META['SERVER_PORT'])
- if server_port != (self.is_secure() and '443' or '80'):
+ if server_port != ('443' if self.is_secure() else '80'):
host = '%s:%s' % (host, server_port)
return host
def get_full_path(self):
# RFC 3986 requires query string arguments to be in the ASCII range.
# Rather than crash if this doesn't happen, we encode defensively.
- return '%s%s' % (self.path, self.META.get('QUERY_STRING', '') and ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) or '')
+ return '%s%s' % (self.path, ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) if self.META.get('QUERY_STRING', '') else '')
def get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None):
"""
@@ -193,7 +193,7 @@ class HttpRequest(object):
default argument in which case that value will be returned instead.
"""
try:
- cookie_value = self.COOKIES[key].encode('utf-8')
+ cookie_value = self.COOKIES[key]
except KeyError:
if default is not RAISE_ERROR:
return default
@@ -218,7 +218,7 @@ class HttpRequest(object):
if not location:
location = self.get_full_path()
if not absolute_http_url_re.match(location):
- current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
+ current_uri = '%s://%s%s' % ('https' if self.is_secure() else 'http',
self.get_host(), self.path)
location = urljoin(current_uri, location)
return iri_to_uri(location)
@@ -243,7 +243,12 @@ class HttpRequest(object):
def is_ajax(self):
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
- def _set_encoding(self, val):
+ @property
+ def encoding(self):
+ return self._encoding
+
+ @encoding.setter
+ def encoding(self, val):
"""
Sets the encoding used for GET/POST accesses. If the GET or POST
dictionary has already been created, it is removed and recreated on the
@@ -255,33 +260,28 @@ class HttpRequest(object):
if hasattr(self, '_post'):
del self._post
- def _get_encoding(self):
- return self._encoding
-
- encoding = property(_get_encoding, _set_encoding)
-
def _initialize_handlers(self):
self._upload_handlers = [uploadhandler.load_handler(handler, self)
for handler in settings.FILE_UPLOAD_HANDLERS]
- def _set_upload_handlers(self, upload_handlers):
- if hasattr(self, '_files'):
- raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
- self._upload_handlers = upload_handlers
-
- def _get_upload_handlers(self):
+ @property
+ def upload_handlers(self):
if not self._upload_handlers:
# If there are no upload handlers defined, initialize them from settings.
self._initialize_handlers()
return self._upload_handlers
- upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
+ @upload_handlers.setter
+ def upload_handlers(self, upload_handlers):
+ if hasattr(self, '_files'):
+ raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
+ self._upload_handlers = upload_handlers
def parse_file_upload(self, META, post_data):
"""Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
self.upload_handlers = ImmutableList(
self.upload_handlers,
- warning = "You cannot alter upload handlers after the upload has been processed."
+ warning="You cannot alter upload handlers after the upload has been processed."
)
parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
return parser.parse()
@@ -294,7 +294,7 @@ class HttpRequest(object):
try:
self._body = self.read()
except IOError as e:
- six.reraise(UnreadablePostError, e, sys.exc_traceback)
+ six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
self._stream = BytesIO(self._body)
return self._body
@@ -360,6 +360,7 @@ class HttpRequest(object):
if not buf:
break
yield buf
+
__iter__ = xreadlines
def readlines(self):
@@ -384,29 +385,36 @@ class QueryDict(MultiValueDict):
if not encoding:
encoding = settings.DEFAULT_CHARSET
self.encoding = encoding
- for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
- self.appendlist(force_text(key, encoding, errors='replace'),
- force_text(value, encoding, errors='replace'))
+ if six.PY3:
+ for key, value in parse_qsl(query_string or '',
+ keep_blank_values=True,
+ encoding=encoding):
+ self.appendlist(key, value)
+ else:
+ for key, value in parse_qsl(query_string or '',
+ keep_blank_values=True):
+ self.appendlist(force_text(key, encoding, errors='replace'),
+ force_text(value, encoding, errors='replace'))
self._mutable = mutable
- def _get_encoding(self):
+ @property
+ def encoding(self):
if self._encoding is None:
self._encoding = settings.DEFAULT_CHARSET
return self._encoding
- def _set_encoding(self, value):
+ @encoding.setter
+ def encoding(self, value):
self._encoding = value
- encoding = property(_get_encoding, _set_encoding)
-
def _assert_mutable(self):
if not self._mutable:
raise AttributeError("This QueryDict instance is immutable")
def __setitem__(self, key, value):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- value = str_to_unicode(value, self.encoding)
+ key = bytes_to_text(key, self.encoding)
+ value = bytes_to_text(value, self.encoding)
super(QueryDict, self).__setitem__(key, value)
def __delitem__(self, key):
@@ -415,21 +423,21 @@ class QueryDict(MultiValueDict):
def __copy__(self):
result = self.__class__('', mutable=True, encoding=self.encoding)
- for key, value in self.iterlists():
+ for key, value in six.iterlists(self):
result.setlist(key, value)
return result
def __deepcopy__(self, memo):
result = self.__class__('', mutable=True, encoding=self.encoding)
memo[id(self)] = result
- for key, value in self.iterlists():
+ for key, value in six.iterlists(self):
result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo))
return result
def setlist(self, key, list_):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
+ key = bytes_to_text(key, self.encoding)
+ list_ = [bytes_to_text(elt, self.encoding) for elt in list_]
super(QueryDict, self).setlist(key, list_)
def setlistdefault(self, key, default_list=None):
@@ -438,8 +446,8 @@ class QueryDict(MultiValueDict):
def appendlist(self, key, value):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- value = str_to_unicode(value, self.encoding)
+ key = bytes_to_text(key, self.encoding)
+ value = bytes_to_text(value, self.encoding)
super(QueryDict, self).appendlist(key, value)
def pop(self, key, *args):
@@ -456,8 +464,8 @@ class QueryDict(MultiValueDict):
def setdefault(self, key, default=None):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- default = str_to_unicode(default, self.encoding)
+ key = bytes_to_text(key, self.encoding)
+ default = bytes_to_text(default, self.encoding)
return super(QueryDict, self).setdefault(key, default)
def copy(self):
@@ -481,13 +489,13 @@ class QueryDict(MultiValueDict):
"""
output = []
if safe:
- safe = smart_bytes(safe, self.encoding)
+ safe = force_bytes(safe, self.encoding)
encode = lambda k, v: '%s=%s' % ((quote(k, safe), quote(v, safe)))
else:
encode = lambda k, v: urlencode({k: v})
for k, list_ in self.lists():
- k = smart_bytes(k, self.encoding)
- output.extend([encode(k, smart_bytes(v, self.encoding))
+ k = force_bytes(k, self.encoding)
+ output.extend([encode(k, force_bytes(v, self.encoding))
for v in list_])
return '&'.join(output)
@@ -531,6 +539,7 @@ class HttpResponse(object):
if not content_type:
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
self._charset)
+ # content is a bytestring. See the content property methods.
self.content = content
self.cookies = SimpleCookie()
if status:
@@ -538,30 +547,40 @@ class HttpResponse(object):
self['Content-Type'] = content_type
- def __str__(self):
- """Full HTTP message, including headers."""
- return '\n'.join(['%s: %s' % (key, value)
- for key, value in self._headers.values()]) \
- + '\n\n' + self.content
+ def serialize(self):
+ """Full HTTP message, including headers, as a bytestring."""
+ headers = [
+ ('%s: %s' % (key, value)).encode('us-ascii')
+ for key, value in self._headers.values()
+ ]
+ return b'\r\n'.join(headers) + b'\r\n\r\n' + self.content
+
+ if six.PY3:
+ __bytes__ = serialize
+ else:
+ __str__ = serialize
def _convert_to_ascii(self, *values):
"""Converts all values to ascii strings."""
for value in values:
- if isinstance(value, six.text_type):
- try:
- if not six.PY3:
- value = value.encode('us-ascii')
- else:
- # In Python 3, use a string in headers,
- # but ensure in only contains ASCII characters.
- value.encode('us-ascii')
- except UnicodeError as e:
- e.reason += ', HTTP response headers must be in US-ASCII format'
- raise
- else:
+ if not isinstance(value, six.string_types):
value = str(value)
+ try:
+ if six.PY3:
+ # Ensure string only contains ASCII
+ value.encode('us-ascii')
+ else:
+ if isinstance(value, str):
+ # Ensure string only contains ASCII
+ value.decode('us-ascii')
+ else:
+ # Convert unicode to an ASCII string
+ value = value.encode('us-ascii')
+ except UnicodeError as e:
+ e.reason += ', HTTP response headers must be in US-ASCII format'
+ raise
if '\n' in value or '\r' in value:
- raise BadHeaderError("Header values can't contain newlines (got %r)" % (value))
+ raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
yield value
def __setitem__(self, header, value):
@@ -650,30 +669,40 @@ class HttpResponse(object):
self.set_cookie(key, max_age=0, path=path, domain=domain,
expires='Thu, 01-Jan-1970 00:00:00 GMT')
- def _get_content(self):
+ @property
+ def content(self):
if self.has_header('Content-Encoding'):
- return b''.join([str(e) for e in self._container])
- return b''.join([smart_bytes(e, self._charset) for e in self._container])
+ def make_bytes(value):
+ if isinstance(value, int):
+ value = six.text_type(value)
+ if isinstance(value, six.text_type):
+ value = value.encode('ascii')
+ # force conversion to bytes in case chunk is a subclass
+ return bytes(value)
+ return b''.join(make_bytes(e) for e in self._container)
+ return b''.join(force_bytes(e, self._charset) for e in self._container)
- def _set_content(self, value):
- if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.text_type)):
+ @content.setter
+ def content(self, value):
+ if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)):
self._container = value
self._base_content_is_iter = True
else:
self._container = [value]
self._base_content_is_iter = False
- content = property(_get_content, _set_content)
-
def __iter__(self):
self._iterator = iter(self._container)
return self
def __next__(self):
chunk = next(self._iterator)
+ if isinstance(chunk, int):
+ chunk = six.text_type(chunk)
if isinstance(chunk, six.text_type):
chunk = chunk.encode(self._charset)
- return str(chunk)
+ # force conversion to bytes in case chunk is a subclass
+ return bytes(chunk)
next = __next__ # Python 2 compatibility
@@ -699,11 +728,11 @@ class HttpResponse(object):
class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
- def __init__(self, redirect_to):
+ def __init__(self, redirect_to, *args, **kwargs):
parsed = urlparse(redirect_to)
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
- super(HttpResponseRedirectBase, self).__init__()
+ super(HttpResponseRedirectBase, self).__init__(*args, **kwargs)
self['Location'] = iri_to_uri(redirect_to)
class HttpResponseRedirect(HttpResponseRedirectBase):
@@ -715,6 +744,16 @@ class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
class HttpResponseNotModified(HttpResponse):
status_code = 304
+ def __init__(self, *args, **kwargs):
+ super(HttpResponseNotModified, self).__init__(*args, **kwargs)
+ del self['content-type']
+
+ @HttpResponse.content.setter
+ def content(self, value):
+ if value:
+ raise AttributeError("You cannot set content to a 304 (Not Modified) response")
+ self._container = []
+
class HttpResponseBadRequest(HttpResponse):
status_code = 400
@@ -727,8 +766,8 @@ class HttpResponseForbidden(HttpResponse):
class HttpResponseNotAllowed(HttpResponse):
status_code = 405
- def __init__(self, permitted_methods):
- super(HttpResponseNotAllowed, self).__init__()
+ def __init__(self, permitted_methods, *args, **kwargs):
+ super(HttpResponseNotAllowed, self).__init__(*args, **kwargs)
self['Allow'] = ', '.join(permitted_methods)
class HttpResponseGone(HttpResponse):
@@ -743,8 +782,8 @@ def get_host(request):
# It's neither necessary nor appropriate to use
# django.utils.encoding.smart_text for parsing URLs and form inputs. Thus,
-# this slightly more restricted function.
-def str_to_unicode(s, encoding):
+# this slightly more restricted function, used by QueryDict.
+def bytes_to_text(s, encoding):
"""
Converts basestring objects to unicode, using the given encoding. Illegally
encoded input characters are replaced with Unicode "unknown" codepoint
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index 9c66827cbb..e76a4f4208 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -6,7 +6,9 @@ file upload handlers for processing.
"""
from __future__ import unicode_literals
+import base64
import cgi
+
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.utils.datastructures import MultiValueDict
@@ -199,7 +201,7 @@ class MultiPartParser(object):
if transfer_encoding == 'base64':
# We only special-case base64 transfer encoding
try:
- chunk = str(chunk).decode('base64')
+ chunk = base64.b64decode(chunk)
except Exception as e:
# Since this is only a chunk, any error is an unfixable error.
raise MultiPartParserError("Could not decode base64 data: %r" % e)
@@ -507,9 +509,11 @@ class BoundaryIter(object):
end = index
next = index + len(self._boundary)
# backup over CRLF
- if data[max(0,end-1)] == b'\n':
+ last = max(0, end-1)
+ if data[last:last+1] == b'\n':
end -= 1
- if data[max(0,end-1)] == b'\r':
+ last = max(0, end-1)
+ if data[last:last+1] == b'\r':
end -= 1
return end, next
@@ -613,7 +617,7 @@ def parse_header(line):
if i >= 0:
name = p[:i].strip().lower().decode('ascii')
value = p[i+1:].strip()
- if len(value) >= 2 and value[0] == value[-1] == b'"':
+ if len(value) >= 2 and value[:1] == value[-1:] == b'"':
value = value[1:-1]
value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"')
pdict[name] = value
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index fd8ff30303..305b20e1c4 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -4,6 +4,7 @@ Cross Site Request Forgery Middleware.
This module provides a middleware that implements protection
against request forgeries from other sites.
"""
+from __future__ import unicode_literals
import hashlib
import re
@@ -12,6 +13,7 @@ import random
from django.conf import settings
from django.core.urlresolvers import get_callable
from django.utils.cache import patch_vary_headers
+from django.utils.encoding import force_text
from django.utils.http import same_origin
from django.utils.log import getLogger
from django.utils.crypto import constant_time_compare, get_random_string
@@ -51,11 +53,10 @@ def get_token(request):
def _sanitize_token(token):
- # Allow only alphanum, and ensure we return a 'str' for the sake
- # of the post processing middleware.
+ # Allow only alphanum
if len(token) > CSRF_KEY_LENGTH:
return _get_new_csrf_key()
- token = re.sub('[^a-zA-Z0-9]+', '', str(token.decode('ascii', 'ignore')))
+ token = re.sub('[^a-zA-Z0-9]+', '', force_text(token))
if token == "":
# In case the cookie has been truncated to nothing at some point.
return _get_new_csrf_key()
diff --git a/django/template/base.py b/django/template/base.py
index 661d8c092a..0a2b2c9437 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -11,7 +11,7 @@ from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable
from django.utils.text import (smart_split, unescape_string_literal,
get_text_list)
-from django.utils.encoding import smart_text, force_text, smart_bytes
+from django.utils.encoding import force_str, force_text
from django.utils.translation import ugettext_lazy, pgettext_lazy
from django.utils.safestring import (SafeData, EscapeData, mark_safe,
mark_for_escaping)
@@ -20,6 +20,7 @@ from django.utils.html import escape
from django.utils.module_loading import module_has_submodule
from django.utils import six
from django.utils.timezone import template_localtime
+from django.utils.encoding import python_2_unicode_compatible
TOKEN_TEXT = 0
@@ -79,6 +80,7 @@ class TemplateDoesNotExist(Exception):
class TemplateEncodingError(Exception):
pass
+@python_2_unicode_compatible
class VariableDoesNotExist(Exception):
def __init__(self, msg, params=()):
@@ -86,9 +88,6 @@ class VariableDoesNotExist(Exception):
self.params = params
def __str__(self):
- return six.text_type(self).encode('utf-8')
-
- def __unicode__(self):
return self.msg % tuple([force_text(p, errors='replace')
for p in self.params])
@@ -117,7 +116,7 @@ class Template(object):
def __init__(self, template_string, origin=None,
name='<Unknown Template>'):
try:
- template_string = smart_text(template_string)
+ template_string = force_text(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed "
"from unicode or UTF-8 strings.")
@@ -849,7 +848,7 @@ class TextNode(Node):
self.s = s
def __repr__(self):
- return "<Text Node: '%s'>" % smart_bytes(self.s[:25], 'ascii',
+ return force_str("<Text Node: '%s'>" % self.s[:25], 'ascii',
errors='replace')
def render(self, context):
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 16801fd573..1bfb627023 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -231,12 +231,12 @@ def make_list(value):
@stringfilter
def slugify(value):
"""
- Normalizes string, converts to lowercase, removes non-alpha characters,
- and converts spaces to hyphens.
+ Converts to lowercase, removes non-word characters (alphanumerics and
+ underscores) and converts spaces to hyphens. Also strips leading and
+ trailing whitespace.
"""
- value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
- value = six.text_type(re.sub('[^\w\s-]', '', value).strip().lower())
- return mark_safe(re.sub('[-\s]+', '-', value))
+ from django.utils.text import slugify
+ return slugify(value)
@register.filter(is_safe=True)
def stringformat(value, arg):
@@ -468,13 +468,8 @@ def safeseq(value):
@stringfilter
def removetags(value, tags):
"""Removes a space separated list of [X]HTML tags from the output."""
- tags = [re.escape(tag) for tag in tags.split()]
- tags_re = '(%s)' % '|'.join(tags)
- starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
- endtag_re = re.compile('</%s>' % tags_re)
- value = starttag_re.sub('', value)
- value = endtag_re.sub('', value)
- return value
+ from django.utils.html import remove_tags
+ return remove_tags(value, tags)
@register.filter(is_safe=True)
@stringfilter
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 14391f08e1..cb2ecd26d8 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -448,7 +448,7 @@ class WidthRatioNode(Node):
max_width = int(self.max_width.resolve(context))
except VariableDoesNotExist:
return ''
- except ValueError:
+ except (ValueError, TypeError):
raise TemplateSyntaxError("widthratio final argument must be an number")
try:
value = float(value)
@@ -456,7 +456,7 @@ class WidthRatioNode(Node):
ratio = (value / max_value) * max_width
except ZeroDivisionError:
return '0'
- except ValueError:
+ except (ValueError, TypeError):
return ''
return str(int(round(ratio)))
diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py
index 42f87a459e..7e35b34ec4 100644
--- a/django/template/loaders/eggs.py
+++ b/django/template/loaders/eggs.py
@@ -1,13 +1,15 @@
# Wrapper for loading templates from eggs via pkg_resources.resource_string.
+from __future__ import unicode_literals
try:
from pkg_resources import resource_string
except ImportError:
resource_string = None
+from django.conf import settings
from django.template.base import TemplateDoesNotExist
from django.template.loader import BaseLoader
-from django.conf import settings
+from django.utils import six
class Loader(BaseLoader):
is_usable = resource_string is not None
@@ -22,9 +24,12 @@ class Loader(BaseLoader):
pkg_name = 'templates/' + template_name
for app in settings.INSTALLED_APPS:
try:
- return (resource_string(app, pkg_name).decode(settings.FILE_CHARSET), 'egg:%s:%s' % (app, pkg_name))
- except:
- pass
+ resource = resource_string(app, pkg_name)
+ except Exception:
+ continue
+ if not six.PY3:
+ resource = resource.decode(settings.FILE_CHARSET)
+ return (resource, 'egg:%s:%s' % (app, pkg_name))
raise TemplateDoesNotExist(template_name)
_loader = Loader()
diff --git a/django/template/response.py b/django/template/response.py
index 800e060c74..2cb44d127d 100644
--- a/django/template/response.py
+++ b/django/template/response.py
@@ -102,7 +102,7 @@ class SimpleTemplateResponse(HttpResponse):
"""
retval = self
if not self._is_rendered:
- self._set_content(self.rendered_content)
+ self.content = self.rendered_content
for post_callback in self._post_render_callbacks:
newretval = post_callback(retval)
if newretval is not None:
@@ -119,20 +119,20 @@ class SimpleTemplateResponse(HttpResponse):
'rendered before it can be iterated over.')
return super(SimpleTemplateResponse, self).__iter__()
- def _get_content(self):
+ @property
+ def content(self):
if not self._is_rendered:
raise ContentNotRenderedError('The response content must be '
'rendered before it can be accessed.')
- return super(SimpleTemplateResponse, self)._get_content()
+ return super(SimpleTemplateResponse, self).content
- def _set_content(self, value):
+ @content.setter
+ def content(self, value):
"""Sets the content for the response
"""
- super(SimpleTemplateResponse, self)._set_content(value)
+ HttpResponse.content.fset(self, value)
self._is_rendered = True
- content = property(_get_content, _set_content)
-
class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + \
diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py
index 056eadc7de..36db4807c7 100644
--- a/django/templatetags/cache.py
+++ b/django/templatetags/cache.py
@@ -4,6 +4,7 @@ import hashlib
from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
from django.template import resolve_variable
from django.core.cache import cache
+from django.utils.encoding import force_bytes
from django.utils.http import urlquote
register = Library()
@@ -24,8 +25,9 @@ class CacheNode(Node):
expire_time = int(expire_time)
except (ValueError, TypeError):
raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
- # Build a unicode key for this fragment and all vary-on's.
- args = hashlib.md5(':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on]))
+ # Build a key for this fragment and all vary-on's.
+ key = ':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])
+ args = hashlib.md5(force_bytes(key))
cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
value = cache.get(cache_key)
if value is None:
diff --git a/django/test/client.py b/django/test/client.py
index a9ae7f5db1..8fd765ec9a 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import sys
import os
import re
@@ -19,7 +21,7 @@ from django.http import SimpleCookie, HttpRequest, QueryDict
from django.template import TemplateDoesNotExist
from django.test import signals
from django.utils.functional import curry
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_bytes
from django.utils.http import urlencode
from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable
@@ -108,7 +110,7 @@ def encode_multipart(boundary, data):
as an application/octet-stream; otherwise, str(value) will be sent.
"""
lines = []
- to_str = lambda s: smart_bytes(s, settings.DEFAULT_CHARSET)
+ to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
# Not by any means perfect, but good enough for our purposes.
is_file = lambda thing: hasattr(thing, "read") and callable(thing.read)
@@ -124,37 +126,37 @@ def encode_multipart(boundary, data):
if is_file(item):
lines.extend(encode_file(boundary, key, item))
else:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ lines.extend([to_bytes(val) for val in [
+ '--%s' % boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
'',
- to_str(item)
- ])
+ item
+ ]])
else:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ lines.extend([to_bytes(val) for val in [
+ '--%s' % boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
'',
- to_str(value)
- ])
+ value
+ ]])
lines.extend([
- '--' + boundary + '--',
- '',
+ to_bytes('--%s--' % boundary),
+ b'',
])
- return '\r\n'.join(lines)
+ return b'\r\n'.join(lines)
def encode_file(boundary, key, file):
- to_str = lambda s: smart_bytes(s, settings.DEFAULT_CHARSET)
+ to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
content_type = mimetypes.guess_type(file.name)[0]
if content_type is None:
content_type = 'application/octet-stream'
return [
- '--' + to_str(boundary),
- 'Content-Disposition: form-data; name="%s"; filename="%s"' \
- % (to_str(key), to_str(os.path.basename(file.name))),
- 'Content-Type: %s' % content_type,
- '',
+ to_bytes('--%s' % boundary),
+ to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' \
+ % (key, os.path.basename(file.name))),
+ to_bytes('Content-Type: %s' % content_type),
+ b'',
file.read()
]
@@ -220,7 +222,7 @@ class RequestFactory(object):
charset = match.group(1)
else:
charset = settings.DEFAULT_CHARSET
- return smart_bytes(data, encoding=charset)
+ return force_bytes(data, encoding=charset)
def _get_path(self, parsed):
# If there are parameters, add them
@@ -291,7 +293,7 @@ class RequestFactory(object):
def generic(self, method, path,
data='', content_type='application/octet-stream', **extra):
parsed = urlparse(path)
- data = smart_bytes(data, settings.DEFAULT_CHARSET)
+ data = force_bytes(data, settings.DEFAULT_CHARSET)
r = {
'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': parsed[4],
@@ -385,7 +387,7 @@ class Client(RequestFactory):
if self.exc_info:
exc_info = self.exc_info
self.exc_info = None
- six.reraise(exc_info[1], None, exc_info[2])
+ six.reraise(*exc_info)
# Save the client and request that stimulated the response.
response.client = self
diff --git a/django/test/html.py b/django/test/html.py
index acdb4ffd14..274810cab4 100644
--- a/django/test/html.py
+++ b/django/test/html.py
@@ -8,6 +8,7 @@ import re
from django.utils.encoding import force_text
from django.utils.html_parser import HTMLParser, HTMLParseError
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
WHITESPACE = re.compile('\s+')
@@ -17,6 +18,7 @@ def normalize_whitespace(string):
return WHITESPACE.sub(' ', string)
+@python_2_unicode_compatible
class Element(object):
def __init__(self, name, attributes):
self.name = name
@@ -117,7 +119,7 @@ class Element(object):
def __getitem__(self, key):
return self.children[key]
- def __unicode__(self):
+ def __str__(self):
output = '<%s' % self.name
for key, value in self.attributes:
if value:
@@ -136,11 +138,12 @@ class Element(object):
return six.text_type(self)
+@python_2_unicode_compatible
class RootElement(Element):
def __init__(self):
super(RootElement, self).__init__(None, ())
- def __unicode__(self):
+ def __str__(self):
return ''.join(six.text_type(c) for c in self.children)
diff --git a/django/test/signals.py b/django/test/signals.py
index 052b7dfa5c..5b0a9a19ca 100644
--- a/django/test/signals.py
+++ b/django/test/signals.py
@@ -4,7 +4,6 @@ import time
from django.conf import settings
from django.db import connections
from django.dispatch import receiver, Signal
-from django.template import context
from django.utils import timezone
template_rendered = Signal(providing_args=["template", "context"])
@@ -48,10 +47,18 @@ def update_connections_time_zone(**kwargs):
@receiver(setting_changed)
def clear_context_processors_cache(**kwargs):
if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS':
+ from django.template import context
context._standard_context_processors = None
@receiver(setting_changed)
+def clear_serializers_cache(**kwargs):
+ if kwargs['setting'] == 'SERIALIZATION_MODULES':
+ from django.core import serializers
+ serializers._serializers = {}
+
+
+@receiver(setting_changed)
def language_changed(**kwargs):
if kwargs['setting'] in ('LOCALE_PATHS', 'LANGUAGE_CODE'):
from django.utils.translation import trans_real
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 56ba56caf1..f12c431d3a 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -41,7 +41,7 @@ from django.test.utils import (get_warnings_state, restore_warnings_state,
override_settings)
from django.test.utils import ContextList
from django.utils import unittest as ut2
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_text
from django.utils import six
from django.utils.unittest.util import safe_repr
from django.views.static import serve
@@ -647,14 +647,13 @@ class TransactionTestCase(SimpleTestCase):
self.assertEqual(response.status_code, status_code,
msg_prefix + "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code))
- enc_text = smart_bytes(text, response._charset)
- content = response.content
+ content = response.content.decode(response._charset)
if html:
content = assert_and_parse_html(self, content, None,
"Response's content is not valid HTML:")
- enc_text = assert_and_parse_html(self, enc_text, None,
+ text = assert_and_parse_html(self, text, None,
"Second argument is not valid HTML:")
- real_count = content.count(enc_text)
+ real_count = content.count(text)
if count is not None:
self.assertEqual(real_count, count,
msg_prefix + "Found %d instances of '%s' in response"
@@ -683,14 +682,13 @@ class TransactionTestCase(SimpleTestCase):
self.assertEqual(response.status_code, status_code,
msg_prefix + "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code))
- enc_text = smart_bytes(text, response._charset)
- content = response.content
+ content = response.content.decode(response._charset)
if html:
content = assert_and_parse_html(self, content, None,
'Response\'s content is not valid HTML:')
- enc_text = assert_and_parse_html(self, enc_text, None,
+ text = assert_and_parse_html(self, text, None,
'Second argument is not valid HTML:')
- self.assertEqual(content.count(enc_text), 0,
+ self.assertEqual(content.count(text), 0,
msg_prefix + "Response should not contain '%s'" % text)
def assertFormError(self, response, form, field, errors, msg_prefix=''):
@@ -919,23 +917,26 @@ class QuietWSGIRequestHandler(WSGIRequestHandler):
pass
-class _ImprovedEvent(threading._Event):
- """
- Does the same as `threading.Event` except it overrides the wait() method
- with some code borrowed from Python 2.7 to return the set state of the
- event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
- to know whether the wait() method exited normally or because of the
- timeout. This class can be removed when Django supports only Python >= 2.7.
- """
+if sys.version_info >= (2, 7, 0):
+ _ImprovedEvent = threading._Event
+else:
+ class _ImprovedEvent(threading._Event):
+ """
+ Does the same as `threading.Event` except it overrides the wait() method
+ with some code borrowed from Python 2.7 to return the set state of the
+ event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
+ to know whether the wait() method exited normally or because of the
+ timeout. This class can be removed when Django supports only Python >= 2.7.
+ """
- def wait(self, timeout=None):
- self._Event__cond.acquire()
- try:
- if not self._Event__flag:
- self._Event__cond.wait(timeout)
- return self._Event__flag
- finally:
- self._Event__cond.release()
+ def wait(self, timeout=None):
+ self._Event__cond.acquire()
+ try:
+ if not self._Event__flag:
+ self._Event__cond.wait(timeout)
+ return self._Event__flag
+ finally:
+ self._Event__cond.release()
class StoppableWSGIServer(WSGIServer):
@@ -1136,7 +1137,7 @@ class LiveServerTestCase(TransactionTestCase):
host, port_ranges = specified_address.split(':')
for port_range in port_ranges.split(','):
# A port range can be of either form: '8000' or '8000-8010'.
- extremes = map(int, port_range.split('-'))
+ extremes = list(map(int, port_range.split('-')))
assert len(extremes) in [1, 2]
if len(extremes) == 1:
# Port range of the form '8000'
diff --git a/django/test/utils.py b/django/test/utils.py
index 4b121bdfb0..e2c439fd80 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -221,4 +221,4 @@ class override_settings(object):
setting=key, value=new_value)
def str_prefix(s):
- return s % {'_': 'u'}
+ return s % {'_': '' if six.PY3 else 'u'}
diff --git a/django/utils/2to3_fixes/__init__.py b/django/utils/2to3_fixes/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/utils/2to3_fixes/__init__.py
diff --git a/django/utils/2to3_fixes/fix_unicode.py b/django/utils/2to3_fixes/fix_unicode.py
new file mode 100644
index 0000000000..613734ca86
--- /dev/null
+++ b/django/utils/2to3_fixes/fix_unicode.py
@@ -0,0 +1,36 @@
+"""Fixer for __unicode__ methods.
+
+Uses the django.utils.encoding.python_2_unicode_compatible decorator.
+"""
+
+from __future__ import unicode_literals
+
+from lib2to3 import fixer_base
+from lib2to3.fixer_util import find_indentation, Name, syms, touch_import
+from lib2to3.pgen2 import token
+from lib2to3.pytree import Leaf, Node
+
+
+class FixUnicode(fixer_base.BaseFix):
+
+ BM_compatible = True
+ PATTERN = """
+ classdef< 'class' any+ ':'
+ suite< any*
+ funcdef< 'def' unifunc='__unicode__'
+ parameters< '(' NAME ')' > any+ >
+ any* > >
+ """
+
+ def transform(self, node, results):
+ unifunc = results["unifunc"]
+ strfunc = Name("__str__", prefix=unifunc.prefix)
+ unifunc.replace(strfunc)
+
+ klass = node.clone()
+ klass.prefix = '\n' + find_indentation(node)
+ decorator = Node(syms.decorator, [Leaf(token.AT, "@"), Name('python_2_unicode_compatible')])
+ decorated = Node(syms.decorated, [decorator, klass], prefix=node.prefix)
+ node.replace(decorated)
+
+ touch_import('django.utils.encoding', 'python_2_unicode_compatible', decorated)
diff --git a/django/utils/archive.py b/django/utils/archive.py
index 829b55dd28..0faf1fa781 100644
--- a/django/utils/archive.py
+++ b/django/utils/archive.py
@@ -46,7 +46,8 @@ def extract(path, to_path=''):
Unpack the tar or zip file at the specified path to the directory
specified by to_path.
"""
- Archive(path).extract(to_path)
+ with Archive(path) as archive:
+ archive.extract(to_path)
class Archive(object):
@@ -77,12 +78,21 @@ class Archive(object):
"Path not a recognized archive format: %s" % filename)
return cls
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
def extract(self, to_path=''):
self._archive.extract(to_path)
def list(self):
self._archive.list()
+ def close(self):
+ self._archive.close()
+
class BaseArchive(object):
"""
@@ -161,6 +171,9 @@ class TarArchive(BaseArchive):
if extracted:
extracted.close()
+ def close(self):
+ self._archive.close()
+
class ZipArchive(BaseArchive):
@@ -189,6 +202,9 @@ class ZipArchive(BaseArchive):
with open(filename, 'wb') as outfile:
outfile.write(data)
+ def close(self):
+ self._archive.close()
+
extension_map = {
'.tar': TarArchive,
'.tar.bz2': TarArchive,
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index b6c055383c..2daafedd85 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -31,7 +31,7 @@
import os, sys, time, signal
try:
- import thread
+ from django.utils.six.moves import _thread as thread
except ImportError:
from django.utils.six.moves import _dummy_thread as thread
@@ -54,7 +54,8 @@ _win = (sys.platform == "win32")
def code_changed():
global _mtimes, _win
- for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())):
+ filenames = [getattr(m, "__file__", None) for m in sys.modules.values()]
+ for filename in filter(None, filenames):
if filename.endswith(".pyc") or filename.endswith(".pyo"):
filename = filename[:-1]
if filename.endswith("$py.class"):
diff --git a/django/utils/cache.py b/django/utils/cache.py
index 42b0de4ce6..91c4796988 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -16,6 +16,7 @@ cache keys to prevent delivery of wrong content.
An example: i18n middleware would need to distinguish caches by the
"Accept-language" header.
"""
+from __future__ import unicode_literals
import hashlib
import re
@@ -23,7 +24,7 @@ import time
from django.conf import settings
from django.core.cache import get_cache
-from django.utils.encoding import iri_to_uri, force_text
+from django.utils.encoding import iri_to_uri, force_bytes, force_text
from django.utils.http import http_date
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language
@@ -65,7 +66,7 @@ def patch_cache_control(response, **kwargs):
# max-age, use the minimum of the two ages. In practice this happens when
# a decorator and a piece of middleware both operate on a given view.
if 'max-age' in cc and 'max_age' in kwargs:
- kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
+ kwargs['max_age'] = min(int(cc['max-age']), kwargs['max_age'])
# Allow overriding private caching and vice versa
if 'private' in cc and 'public' in kwargs:
@@ -170,7 +171,7 @@ def _i18n_cache_key_suffix(request, cache_key):
# User-defined tzinfo classes may return absolutely anything.
# Hence this paranoid conversion to create a valid cache key.
tz_name = force_text(get_current_timezone_name(), errors='ignore')
- cache_key += '.%s' % tz_name.encode('ascii', 'ignore').replace(' ', '_')
+ cache_key += '.%s' % tz_name.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
return cache_key
def _generate_cache_key(request, method, headerlist, key_prefix):
@@ -180,14 +181,14 @@ def _generate_cache_key(request, method, headerlist, key_prefix):
value = request.META.get(header, None)
if value is not None:
ctx.update(value)
- path = hashlib.md5(iri_to_uri(request.get_full_path()))
+ path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
key_prefix, method, path.hexdigest(), ctx.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
def _generate_cache_header_key(key_prefix, request):
"""Returns a cache key for the header cache."""
- path = hashlib.md5(iri_to_uri(request.get_full_path()))
+ path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, path.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
diff --git a/django/utils/crypto.py b/django/utils/crypto.py
index 70a07e7fde..57bc60dc4f 100644
--- a/django/utils/crypto.py
+++ b/django/utils/crypto.py
@@ -23,7 +23,8 @@ except NotImplementedError:
using_sysrandom = False
from django.conf import settings
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_bytes
+from django.utils import six
from django.utils.six.moves import xrange
@@ -50,7 +51,7 @@ def salted_hmac(key_salt, value, secret=None):
# line is redundant and could be replaced by key = key_salt + secret, since
# the hmac module does the same thing for keys longer than the block size.
# However, we need to ensure that we *always* do this.
- return hmac.new(key, msg=smart_bytes(value), digestmod=hashlib.sha1)
+ return hmac.new(key, msg=force_bytes(value), digestmod=hashlib.sha1)
def get_random_string(length=12,
@@ -88,8 +89,12 @@ def constant_time_compare(val1, val2):
if len(val1) != len(val2):
return False
result = 0
- for x, y in zip(val1, val2):
- result |= ord(x) ^ ord(y)
+ if six.PY3 and isinstance(val1, bytes) and isinstance(val2, bytes):
+ for x, y in zip(val1, val2):
+ result |= x ^ y
+ else:
+ for x, y in zip(val1, val2):
+ result |= ord(x) ^ ord(y)
return result == 0
@@ -142,8 +147,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
assert iterations > 0
if not digest:
digest = hashlib.sha256
- password = smart_bytes(password)
- salt = smart_bytes(salt)
+ password = force_bytes(password)
+ salt = force_bytes(salt)
hlen = digest().digest_size
if not dklen:
dklen = hlen
diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py
index 032eb493b6..b4bd559a66 100644
--- a/django/utils/dateparse.py
+++ b/django/utils/dateparse.py
@@ -15,16 +15,16 @@ date_re = re.compile(
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
)
-datetime_re = re.compile(
- r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
- r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+time_re = re.compile(
+ r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
- r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
)
-time_re = re.compile(
- r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+datetime_re = re.compile(
+ r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
+ r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
+ r'(?P<tzinfo>Z|[+-]\d{2}:?\d{2})?$'
)
def parse_date(value):
@@ -43,8 +43,6 @@ def parse_time(value):
This function doesn't support time zone offsets.
- Sub-microsecond precision is accepted, but ignored.
-
Raises ValueError if the input is well formatted but not a valid time.
Returns None if the input isn't well formatted, in particular if it
contains an offset.
@@ -63,8 +61,6 @@ def parse_datetime(value):
This function supports time zone offsets. When the input contains one,
the output uses an instance of FixedOffset as tzinfo.
- Sub-microsecond precision is accepted, but ignored.
-
Raises ValueError if the input is well formatted but not a valid datetime.
Returns None if the input isn't well formatted.
"""
@@ -77,7 +73,7 @@ def parse_datetime(value):
if tzinfo == 'Z':
tzinfo = utc
elif tzinfo is not None:
- offset = 60 * int(tzinfo[1:3]) + int(tzinfo[4:6])
+ offset = 60 * int(tzinfo[1:3]) + int(tzinfo[-2:])
if tzinfo[0] == '-':
offset = -offset
tzinfo = FixedOffset(offset)
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index eb60cfde8b..3b284f3ed0 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -8,6 +8,7 @@ try:
from urllib.parse import quote
except ImportError: # Python 2
from urllib import quote
+import warnings
from django.utils.functional import Promise
from django.utils import six
@@ -32,6 +33,12 @@ class StrAndUnicode(object):
Useful as a mix-in. If you support Python 2 and 3 with a single code base,
you can inherit this mix-in and just define __unicode__.
"""
+ def __init__(self, *args, **kwargs):
+ warnings.warn("StrAndUnicode is deprecated. Define a __str__ method "
+ "and apply the @python_2_unicode_compatible decorator "
+ "instead.", PendingDeprecationWarning, stacklevel=2)
+ super(StrAndUnicode, self).__init__(*args, **kwargs)
+
if six.PY3:
def __str__(self):
return self.__unicode__()
@@ -39,6 +46,19 @@ class StrAndUnicode(object):
def __str__(self):
return self.__unicode__().encode('utf-8')
+def python_2_unicode_compatible(klass):
+ """
+ A decorator that defines __unicode__ and __str__ methods under Python 2.
+ Under Python 3 it does nothing.
+
+ To support Python 2 and 3 with a single code base, define a __str__ method
+ returning text and apply this decorator to the class.
+ """
+ if not six.PY3:
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
Returns a text object representing 's' -- unicode on Python 2 and str on
@@ -99,8 +119,8 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
errors) for arg in s])
else:
# Note: We use .decode() here, instead of six.text_type(s, encoding,
- # errors), so that if s is a SafeString, it ends up being a
- # SafeUnicode at the end.
+ # errors), so that if s is a SafeBytes, it ends up being a
+ # SafeText at the end.
s = s.decode(encoding, errors)
except UnicodeDecodeError as e:
if not isinstance(s, Exception):
@@ -121,6 +141,19 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
If strings_only is True, don't convert (some) non-string-like objects.
"""
+ if isinstance(s, Promise):
+ # The input is the result of a gettext_lazy() call.
+ return s
+ return force_bytes(s, encoding, strings_only, errors)
+
+
+def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Similar to smart_bytes, except that lazy instances are resolved to
+ strings, rather than kept as lazy objects.
+
+ If strings_only is True, don't convert (some) non-string-like objects.
+ """
if isinstance(s, bytes):
if encoding == 'utf-8':
return s
@@ -141,7 +174,7 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
# An Exception subclass containing non-ASCII data that doesn't
# know how to print itself properly. We shouldn't raise a
# further exception.
- return ' '.join([smart_bytes(arg, encoding, strings_only,
+ return b' '.join([force_bytes(arg, encoding, strings_only,
errors) for arg in s])
return six.text_type(s).encode(encoding, errors)
else:
@@ -149,8 +182,10 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
if six.PY3:
smart_str = smart_text
+ force_str = force_text
else:
smart_str = smart_bytes
+ force_str = force_bytes
# backwards compatibility for Python 2
smart_unicode = smart_text
force_unicode = force_text
@@ -161,6 +196,10 @@ Apply smart_text in Python 3 and smart_bytes in Python 2.
This is suitable for writing to sys.stdout (for instance).
"""
+force_str.__doc__ = """\
+Apply force_text in Python 3 and force_bytes in Python 2.
+"""
+
def iri_to_uri(iri):
"""
Convert an Internationalized Resource Identifier (IRI) portion to a URI
@@ -186,7 +225,7 @@ def iri_to_uri(iri):
# converted.
if iri is None:
return iri
- return quote(smart_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
+ return quote(force_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
def filepath_to_uri(path):
"""Convert an file system path to a URI portion that is suitable for
@@ -205,7 +244,7 @@ def filepath_to_uri(path):
return path
# I know about `os.sep` and `os.altsep` but I want to leave
# some flexibility for hardcoding separators.
- return quote(smart_bytes(path).replace("\\", "/"), safe=b"/~!*()'")
+ return quote(force_bytes(path.replace("\\", "/")), safe=b"/~!*()'")
# The encoding of the default system locale but falls back to the
# given fallback encoding if the encoding is unsupported by python or could
diff --git a/django/utils/formats.py b/django/utils/formats.py
index 2e54d792fa..555982eede 100644
--- a/django/utils/formats.py
+++ b/django/utils/formats.py
@@ -4,7 +4,7 @@ import datetime
from django.conf import settings
from django.utils import dateformat, numberformat, datetime_safe
from django.utils.importlib import import_module
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_str
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from django.utils import six
@@ -66,7 +66,7 @@ def get_format(format_type, lang=None, use_l10n=None):
If use_l10n is provided and is not None, that will force the value to
be localized (or not), overriding the value of settings.USE_L10N.
"""
- format_type = smart_str(format_type)
+ format_type = force_str(format_type)
if use_l10n or (use_l10n is None and settings.USE_L10N):
if lang is None:
lang = get_language()
@@ -160,14 +160,14 @@ def localize_input(value, default=None):
return number_format(value)
elif isinstance(value, datetime.datetime):
value = datetime_safe.new_datetime(value)
- format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
+ format = force_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
return value.strftime(format)
elif isinstance(value, datetime.date):
value = datetime_safe.new_date(value)
- format = smart_str(default or get_format('DATE_INPUT_FORMATS')[0])
+ format = force_str(default or get_format('DATE_INPUT_FORMATS')[0])
return value.strftime(format)
elif isinstance(value, datetime.time):
- format = smart_str(default or get_format('TIME_INPUT_FORMATS')[0])
+ format = force_str(default or get_format('TIME_INPUT_FORMATS')[0])
return value.strftime(format)
return value
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 085ec40b63..085a8fce59 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -238,7 +238,6 @@ class LazyObject(object):
raise NotImplementedError
# introspection support:
- __members__ = property(lambda self: self.__dir__())
__dir__ = new_method_proxy(dir)
diff --git a/django/utils/html.py b/django/utils/html.py
index 7dd53a1353..2b669cc8ec 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -11,7 +11,7 @@ except ImportError: # Python 2
from urlparse import urlsplit, urlunsplit
from django.utils.safestring import SafeData, mark_safe
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_bytes, force_text
from django.utils.functional import allow_lazy
from django.utils import six
from django.utils.text import normalize_newlines
@@ -123,6 +123,17 @@ def strip_tags(value):
return re.sub(r'<[^>]*?>', '', force_text(value))
strip_tags = allow_lazy(strip_tags)
+def remove_tags(html, tags):
+ """Returns the given HTML with given tags removed."""
+ tags = [re.escape(tag) for tag in tags.split()]
+ tags_re = '(%s)' % '|'.join(tags)
+ starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
+ endtag_re = re.compile('</%s>' % tags_re)
+ html = starttag_re.sub('', html)
+ html = endtag_re.sub('', html)
+ return html
+remove_tags = allow_lazy(remove_tags, six.text_type)
+
def strip_spaces_between_tags(value):
"""Returns the given HTML with spaces between tags removed."""
return re.sub(r'>\s+<', '><', force_text(value))
@@ -143,7 +154,7 @@ def smart_urlquote(url):
# Handle IDN before quoting.
scheme, netloc, path, query, fragment = urlsplit(url)
try:
- netloc = netloc.encode('idna') # IDN -> ACE
+ netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
pass
else:
@@ -153,7 +164,7 @@ def smart_urlquote(url):
# contains a % not followed by two hexadecimal digits. See #9655.
if '%' not in url or unquoted_percents_re.search(url):
# See http://bugs.python.org/issue2637
- url = quote(smart_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~')
+ url = quote(force_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~')
return force_text(url)
@@ -206,7 +217,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
elif not ':' in middle and simple_email_re.match(middle):
local, domain = middle.rsplit('@', 1)
try:
- domain = domain.encode('idna')
+ domain = domain.encode('idna').decode('ascii')
except UnicodeError:
continue
url = 'mailto:%s@%s' % (local, domain)
diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py
index ee56c01aec..d7311f253b 100644
--- a/django/utils/html_parser.py
+++ b/django/utils/html_parser.py
@@ -1,102 +1,114 @@
from django.utils.six.moves import html_parser as _html_parser
import re
+import sys
-tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*')
+current_version = sys.version_info
+
+use_workaround = (
+ (current_version < (2, 6, 8)) or
+ (current_version >= (2, 7) and current_version < (2, 7, 3)) or
+ (current_version >= (3, 0) and current_version < (3, 2, 3))
+)
HTMLParseError = _html_parser.HTMLParseError
-class HTMLParser(_html_parser.HTMLParser):
- """
- Patched version of stdlib's HTMLParser with patch from:
- http://bugs.python.org/issue670664
- """
- def __init__(self):
- _html_parser.HTMLParser.__init__(self)
- self.cdata_tag = None
+if not use_workaround:
+ HTMLParser = _html_parser.HTMLParser
+else:
+ tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*')
- def set_cdata_mode(self, tag):
- try:
- self.interesting = _html_parser.interesting_cdata
- except AttributeError:
- self.interesting = re.compile(r'</\s*%s\s*>' % tag.lower(), re.I)
- self.cdata_tag = tag.lower()
+ class HTMLParser(_html_parser.HTMLParser):
+ """
+ Patched version of stdlib's HTMLParser with patch from:
+ http://bugs.python.org/issue670664
+ """
+ def __init__(self):
+ _html_parser.HTMLParser.__init__(self)
+ self.cdata_tag = None
- def clear_cdata_mode(self):
- self.interesting = _html_parser.interesting_normal
- self.cdata_tag = None
+ def set_cdata_mode(self, tag):
+ try:
+ self.interesting = _html_parser.interesting_cdata
+ except AttributeError:
+ self.interesting = re.compile(r'</\s*%s\s*>' % tag.lower(), re.I)
+ self.cdata_tag = tag.lower()
- # Internal -- handle starttag, return end or -1 if not terminated
- def parse_starttag(self, i):
- self.__starttag_text = None
- endpos = self.check_for_whole_start_tag(i)
- if endpos < 0:
- return endpos
- rawdata = self.rawdata
- self.__starttag_text = rawdata[i:endpos]
+ def clear_cdata_mode(self):
+ self.interesting = _html_parser.interesting_normal
+ self.cdata_tag = None
- # Now parse the data between i+1 and j into a tag and attrs
- attrs = []
- match = tagfind.match(rawdata, i + 1)
- assert match, 'unexpected call to parse_starttag()'
- k = match.end()
- self.lasttag = tag = match.group(1).lower()
+ # Internal -- handle starttag, return end or -1 if not terminated
+ def parse_starttag(self, i):
+ self.__starttag_text = None
+ endpos = self.check_for_whole_start_tag(i)
+ if endpos < 0:
+ return endpos
+ rawdata = self.rawdata
+ self.__starttag_text = rawdata[i:endpos]
- while k < endpos:
- m = _html_parser.attrfind.match(rawdata, k)
- if not m:
- break
- attrname, rest, attrvalue = m.group(1, 2, 3)
- if not rest:
- attrvalue = None
- elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
- attrvalue[:1] == '"' == attrvalue[-1:]:
- attrvalue = attrvalue[1:-1]
- if attrvalue:
- attrvalue = self.unescape(attrvalue)
- attrs.append((attrname.lower(), attrvalue))
- k = m.end()
+ # Now parse the data between i+1 and j into a tag and attrs
+ attrs = []
+ match = tagfind.match(rawdata, i + 1)
+ assert match, 'unexpected call to parse_starttag()'
+ k = match.end()
+ self.lasttag = tag = match.group(1).lower()
- end = rawdata[k:endpos].strip()
- if end not in (">", "/>"):
- lineno, offset = self.getpos()
- if "\n" in self.__starttag_text:
- lineno = lineno + self.__starttag_text.count("\n")
- offset = len(self.__starttag_text) \
- - self.__starttag_text.rfind("\n")
+ while k < endpos:
+ m = _html_parser.attrfind.match(rawdata, k)
+ if not m:
+ break
+ attrname, rest, attrvalue = m.group(1, 2, 3)
+ if not rest:
+ attrvalue = None
+ elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
+ attrvalue[:1] == '"' == attrvalue[-1:]:
+ attrvalue = attrvalue[1:-1]
+ if attrvalue:
+ attrvalue = self.unescape(attrvalue)
+ attrs.append((attrname.lower(), attrvalue))
+ k = m.end()
+
+ end = rawdata[k:endpos].strip()
+ if end not in (">", "/>"):
+ lineno, offset = self.getpos()
+ if "\n" in self.__starttag_text:
+ lineno = lineno + self.__starttag_text.count("\n")
+ offset = len(self.__starttag_text) \
+ - self.__starttag_text.rfind("\n")
+ else:
+ offset = offset + len(self.__starttag_text)
+ self.error("junk characters in start tag: %r"
+ % (rawdata[k:endpos][:20],))
+ if end.endswith('/>'):
+ # XHTML-style empty tag: <span attr="value" />
+ self.handle_startendtag(tag, attrs)
else:
- offset = offset + len(self.__starttag_text)
- self.error("junk characters in start tag: %r"
- % (rawdata[k:endpos][:20],))
- if end.endswith('/>'):
- # XHTML-style empty tag: <span attr="value" />
- self.handle_startendtag(tag, attrs)
- else:
- self.handle_starttag(tag, attrs)
- if tag in self.CDATA_CONTENT_ELEMENTS:
- self.set_cdata_mode(tag) # <--------------------------- Changed
- return endpos
+ self.handle_starttag(tag, attrs)
+ if tag in self.CDATA_CONTENT_ELEMENTS:
+ self.set_cdata_mode(tag) # <--------------------------- Changed
+ return endpos
- # Internal -- parse endtag, return end or -1 if incomplete
- def parse_endtag(self, i):
- rawdata = self.rawdata
- assert rawdata[i:i + 2] == "</", "unexpected call to parse_endtag"
- match = _html_parser.endendtag.search(rawdata, i + 1) # >
- if not match:
- return -1
- j = match.end()
- match = _html_parser.endtagfind.match(rawdata, i) # </ + tag + >
- if not match:
- if self.cdata_tag is not None: # *** add ***
- self.handle_data(rawdata[i:j]) # *** add ***
- return j # *** add ***
- self.error("bad end tag: %r" % (rawdata[i:j],))
- # --- changed start ---------------------------------------------------
- tag = match.group(1).strip()
- if self.cdata_tag is not None:
- if tag.lower() != self.cdata_tag:
- self.handle_data(rawdata[i:j])
- return j
- # --- changed end -----------------------------------------------------
- self.handle_endtag(tag.lower())
- self.clear_cdata_mode()
- return j
+ # Internal -- parse endtag, return end or -1 if incomplete
+ def parse_endtag(self, i):
+ rawdata = self.rawdata
+ assert rawdata[i:i + 2] == "</", "unexpected call to parse_endtag"
+ match = _html_parser.endendtag.search(rawdata, i + 1) # >
+ if not match:
+ return -1
+ j = match.end()
+ match = _html_parser.endtagfind.match(rawdata, i) # </ + tag + >
+ if not match:
+ if self.cdata_tag is not None: # *** add ***
+ self.handle_data(rawdata[i:j]) # *** add ***
+ return j # *** add ***
+ self.error("bad end tag: %r" % (rawdata[i:j],))
+ # --- changed start ---------------------------------------------------
+ tag = match.group(1).strip()
+ if self.cdata_tag is not None:
+ if tag.lower() != self.cdata_tag:
+ self.handle_data(rawdata[i:j])
+ return j
+ # --- changed end -----------------------------------------------------
+ self.handle_endtag(tag.lower())
+ self.clear_cdata_mode()
+ return j
diff --git a/django/utils/http.py b/django/utils/http.py
index 22e81a33d7..d3c70f1209 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import calendar
import datetime
import re
@@ -13,7 +15,7 @@ except ImportError: # Python 2
from email.utils import formatdate
from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import force_text, smart_str
+from django.utils.encoding import force_str, force_text
from django.utils.functional import allow_lazy
from django.utils import six
@@ -37,7 +39,7 @@ def urlquote(url, safe='/'):
can safely be used as part of an argument to a subsequent iri_to_uri() call
without double-quoting occurring.
"""
- return force_text(urllib_parse.quote(smart_str(url), smart_str(safe)))
+ return force_text(urllib_parse.quote(force_str(url), force_str(safe)))
urlquote = allow_lazy(urlquote, six.text_type)
def urlquote_plus(url, safe=''):
@@ -47,7 +49,7 @@ def urlquote_plus(url, safe=''):
returned string can safely be used as part of an argument to a subsequent
iri_to_uri() call without double-quoting occurring.
"""
- return force_text(urllib_parse.quote_plus(smart_str(url), smart_str(safe)))
+ return force_text(urllib_parse.quote_plus(force_str(url), force_str(safe)))
urlquote_plus = allow_lazy(urlquote_plus, six.text_type)
def urlunquote(quoted_url):
@@ -55,7 +57,7 @@ def urlunquote(quoted_url):
A wrapper for Python's urllib.unquote() function that can operate on
the result of django.utils.http.urlquote().
"""
- return force_text(urllib_parse.unquote(smart_str(quoted_url)))
+ return force_text(urllib_parse.unquote(force_str(quoted_url)))
urlunquote = allow_lazy(urlunquote, six.text_type)
def urlunquote_plus(quoted_url):
@@ -63,7 +65,7 @@ def urlunquote_plus(quoted_url):
A wrapper for Python's urllib.unquote_plus() function that can operate on
the result of django.utils.http.urlquote_plus().
"""
- return force_text(urllib_parse.unquote_plus(smart_str(quoted_url)))
+ return force_text(urllib_parse.unquote_plus(force_str(quoted_url)))
urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type)
def urlencode(query, doseq=0):
@@ -77,8 +79,8 @@ def urlencode(query, doseq=0):
elif hasattr(query, 'items'):
query = query.items()
return urllib_parse.urlencode(
- [(smart_str(k),
- [smart_str(i) for i in v] if isinstance(v, (list,tuple)) else smart_str(v))
+ [(force_str(k),
+ [force_str(i) for i in v] if isinstance(v, (list,tuple)) else force_str(v))
for k, v in query],
doseq)
@@ -211,7 +213,7 @@ def parse_etags(etag_str):
if not etags:
# etag_str has wrong format, treat it as an opaque string then
return [etag_str]
- etags = [e.decode('string_escape') for e in etags]
+ etags = [e.encode('ascii').decode('unicode_escape') for e in etags]
return etags
def quote_etag(etag):
diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py
index 2ccae8cdd0..7624bb9c76 100644
--- a/django/utils/ipv6.py
+++ b/django/utils/ipv6.py
@@ -263,6 +263,6 @@ def _is_shorthand_ip(ip_str):
"""
if ip_str.count('::') == 1:
return True
- if filter(lambda x: len(x) < 4, ip_str.split(':')):
+ if any(len(x) < 4 for x in ip_str.split(':')):
return True
return False
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
index bfaefd07ee..07e0bf4cea 100644
--- a/django/utils/safestring.py
+++ b/django/utils/safestring.py
@@ -10,36 +10,43 @@ from django.utils import six
class EscapeData(object):
pass
-class EscapeString(bytes, EscapeData):
+class EscapeBytes(bytes, EscapeData):
"""
- A string that should be HTML-escaped when output.
+ A byte string that should be HTML-escaped when output.
"""
pass
-class EscapeUnicode(six.text_type, EscapeData):
+class EscapeText(six.text_type, EscapeData):
"""
- A unicode object that should be HTML-escaped when output.
+ A unicode string object that should be HTML-escaped when output.
"""
pass
+if six.PY3:
+ EscapeString = EscapeText
+else:
+ EscapeString = EscapeBytes
+ # backwards compatibility for Python 2
+ EscapeUnicode = EscapeText
+
class SafeData(object):
pass
-class SafeString(bytes, SafeData):
+class SafeBytes(bytes, SafeData):
"""
- A string subclass that has been specifically marked as "safe" (requires no
+ A bytes subclass that has been specifically marked as "safe" (requires no
further escaping) for HTML output purposes.
"""
def __add__(self, rhs):
"""
- Concatenating a safe string with another safe string or safe unicode
- object is safe. Otherwise, the result is no longer safe.
+ Concatenating a safe byte string with another safe byte string or safe
+ unicode string is safe. Otherwise, the result is no longer safe.
"""
- t = super(SafeString, self).__add__(rhs)
- if isinstance(rhs, SafeUnicode):
- return SafeUnicode(t)
- elif isinstance(rhs, SafeString):
- return SafeString(t)
+ t = super(SafeBytes, self).__add__(rhs)
+ if isinstance(rhs, SafeText):
+ return SafeText(t)
+ elif isinstance(rhs, SafeBytes):
+ return SafeBytes(t)
return t
def _proxy_method(self, *args, **kwargs):
@@ -51,25 +58,25 @@ class SafeString(bytes, SafeData):
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
if isinstance(data, bytes):
- return SafeString(data)
+ return SafeBytes(data)
else:
- return SafeUnicode(data)
+ return SafeText(data)
decode = curry(_proxy_method, method=bytes.decode)
-class SafeUnicode(six.text_type, SafeData):
+class SafeText(six.text_type, SafeData):
"""
- A unicode subclass that has been specifically marked as "safe" for HTML
- output purposes.
+ A unicode (Python 2) / str (Python 3) subclass that has been specifically
+ marked as "safe" for HTML output purposes.
"""
def __add__(self, rhs):
"""
- Concatenating a safe unicode object with another safe string or safe
- unicode object is safe. Otherwise, the result is no longer safe.
+ Concatenating a safe unicode string with another safe byte string or
+ safe unicode string is safe. Otherwise, the result is no longer safe.
"""
- t = super(SafeUnicode, self).__add__(rhs)
+ t = super(SafeText, self).__add__(rhs)
if isinstance(rhs, SafeData):
- return SafeUnicode(t)
+ return SafeText(t)
return t
def _proxy_method(self, *args, **kwargs):
@@ -81,12 +88,19 @@ class SafeUnicode(six.text_type, SafeData):
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
if isinstance(data, bytes):
- return SafeString(data)
+ return SafeBytes(data)
else:
- return SafeUnicode(data)
+ return SafeText(data)
encode = curry(_proxy_method, method=six.text_type.encode)
+if six.PY3:
+ SafeString = SafeText
+else:
+ SafeString = SafeBytes
+ # backwards compatibility for Python 2
+ SafeUnicode = SafeText
+
def mark_safe(s):
"""
Explicitly mark a string as safe for (HTML) output purposes. The returned
@@ -97,10 +111,10 @@ def mark_safe(s):
if isinstance(s, SafeData):
return s
if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
- return SafeString(s)
+ return SafeBytes(s)
if isinstance(s, (six.text_type, Promise)):
- return SafeUnicode(s)
- return SafeString(bytes(s))
+ return SafeText(s)
+ return SafeString(str(s))
def mark_for_escaping(s):
"""
@@ -113,8 +127,8 @@ def mark_for_escaping(s):
if isinstance(s, (SafeData, EscapeData)):
return s
if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
- return EscapeString(s)
+ return EscapeBytes(s)
if isinstance(s, (six.text_type, Promise)):
- return EscapeUnicode(s)
- return EscapeString(bytes(s))
+ return EscapeText(s)
+ return EscapeBytes(bytes(s))
diff --git a/django/utils/six.py b/django/utils/six.py
index ceb5d70e58..e4ce939844 100644
--- a/django/utils/six.py
+++ b/django/utils/six.py
@@ -5,7 +5,7 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
-__version__ = "1.1.0"
+__version__ = "1.2.0"
# True if we are running on Python 3.
@@ -26,19 +26,23 @@ else:
text_type = unicode
binary_type = str
- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
- class X(object):
- def __len__(self):
- return 1 << 31
- try:
- len(X())
- except OverflowError:
- # 32-bit
+ if sys.platform == "java":
+ # Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
- # 64-bit
- MAXSIZE = int((1 << 63) - 1)
- del X
+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+ class X(object):
+ def __len__(self):
+ return 1 << 31
+ try:
+ len(X())
+ except OverflowError:
+ # 32-bit
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # 64-bit
+ MAXSIZE = int((1 << 63) - 1)
+ del X
def _add_doc(func, doc):
@@ -201,12 +205,19 @@ else:
_iteritems = "iteritems"
+try:
+ advance_iterator = next
+except NameError:
+ def advance_iterator(it):
+ return it.next()
+next = advance_iterator
+
+
if PY3:
def get_unbound_function(unbound):
return unbound
-
- advance_iterator = next
+ Iterator = object
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
@@ -214,9 +225,10 @@ else:
def get_unbound_function(unbound):
return unbound.im_func
+ class Iterator(object):
- def advance_iterator(it):
- return it.next()
+ def next(self):
+ return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
@@ -231,15 +243,15 @@ get_function_defaults = operator.attrgetter(_func_defaults)
def iterkeys(d):
"""Return an iterator over the keys of a dictionary."""
- return getattr(d, _iterkeys)()
+ return iter(getattr(d, _iterkeys)())
def itervalues(d):
"""Return an iterator over the values of a dictionary."""
- return getattr(d, _itervalues)()
+ return iter(getattr(d, _itervalues)())
def iteritems(d):
"""Return an iterator over the (key, value) pairs of a dictionary."""
- return getattr(d, _iteritems)()
+ return iter(getattr(d, _iteritems)())
if PY3:
@@ -365,4 +377,6 @@ def iterlists(d):
"""Return an iterator over the values of a MultiValueDict."""
return getattr(d, _iterlists)()
+
add_move(MovedModule("_dummy_thread", "dummy_thread"))
+add_move(MovedModule("_thread", "thread"))
diff --git a/django/utils/text.py b/django/utils/text.py
index 0838d79c65..c19708458b 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -4,13 +4,19 @@ import re
import unicodedata
import warnings
from gzip import GzipFile
-from django.utils.six.moves import html_entities
from io import BytesIO
from django.utils.encoding import force_text
from django.utils.functional import allow_lazy, SimpleLazyObject
from django.utils import six
+from django.utils.six.moves import html_entities
from django.utils.translation import ugettext_lazy, ugettext as _, pgettext
+from django.utils.safestring import mark_safe
+
+if not six.PY3:
+ # Import force_unicode even though this module doesn't use it, because some
+ # people rely on it being here.
+ from django.utils.encoding import force_unicode
# Capitalizes the first letter of a string.
capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:]
@@ -287,7 +293,7 @@ ustring_re = re.compile("([\u0080-\uffff])")
def javascript_quote(s, quote_double_quotes=False):
def fix(match):
- return b"\u%04x" % ord(match.group(1))
+ return "\\u%04x" % ord(match.group(1))
if type(s) == bytes:
s = s.decode('utf-8')
@@ -378,3 +384,14 @@ def unescape_string_literal(s):
quote = s[0]
return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\')
unescape_string_literal = allow_lazy(unescape_string_literal)
+
+def slugify(value):
+ """
+ Converts to lowercase, removes non-word characters (alphanumerics and
+ underscores) and converts spaces to hyphens. Also strips leading and
+ trailing whitespace.
+ """
+ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
+ value = re.sub('[^\w\s-]', '', value).strip().lower()
+ return mark_safe(re.sub('[-\s]+', '-', value))
+slugify = allow_lazy(slugify, six.text_type)
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index febde404a5..f3cc6348f6 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -79,10 +79,10 @@ def pgettext(context, message):
def npgettext(context, singular, plural, number):
return _trans.npgettext(context, singular, plural, number)
-ngettext_lazy = lazy(ngettext, bytes)
-gettext_lazy = lazy(gettext, bytes)
-ungettext_lazy = lazy(ungettext, six.text_type)
+gettext_lazy = lazy(gettext, str)
+ngettext_lazy = lazy(ngettext, str)
ugettext_lazy = lazy(ugettext, six.text_type)
+ungettext_lazy = lazy(ungettext, six.text_type)
pgettext_lazy = lazy(pgettext, six.text_type)
npgettext_lazy = lazy(npgettext, six.text_type)
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 9e6eadcd48..9fd33a7ea8 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -9,7 +9,7 @@ import gettext as gettext_module
from threading import local
from django.utils.importlib import import_module
-from django.utils.encoding import smart_str, smart_text
+from django.utils.encoding import force_str, force_text
from django.utils.safestring import mark_safe, SafeData
from django.utils import six
from django.utils.six import StringIO
@@ -259,6 +259,11 @@ def do_translate(message, translation_function):
return result
def gettext(message):
+ """
+ Returns a string of the translation of the message.
+
+ Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2.
+ """
return do_translate(message, 'gettext')
if six.PY3:
@@ -296,8 +301,10 @@ def do_ntranslate(singular, plural, number, translation_function):
def ngettext(singular, plural, number):
"""
- Returns a UTF-8 bytestring of the translation of either the singular or
- plural, based on the number.
+ Returns a string of the translation of either the singular or plural,
+ based on the number.
+
+ Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2.
"""
return do_ntranslate(singular, plural, number, 'ngettext')
@@ -447,7 +454,7 @@ def templatize(src, origin=None):
from django.conf import settings
from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
- src = smart_text(src, settings.FILE_CHARSET)
+ src = force_text(src, settings.FILE_CHARSET)
out = StringIO()
message_context = None
intrans = False
@@ -462,7 +469,7 @@ def templatize(src, origin=None):
content = ''.join(comment)
translators_comment_start = None
for lineno, line in enumerate(content.splitlines(True)):
- if line.lstrip().startswith(smart_text(TRANSLATOR_COMMENT_MARK)):
+ if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK):
translators_comment_start = lineno
for lineno, line in enumerate(content.splitlines(True)):
if translators_comment_start is not None and lineno >= translators_comment_start:
@@ -577,7 +584,7 @@ def templatize(src, origin=None):
out.write(' # %s' % t.contents)
else:
out.write(blankout(t.contents, 'X'))
- return smart_str(out.getvalue())
+ return force_str(out.getvalue())
def parse_accept_lang_header(lang_string):
"""
diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py
index 208b7e7191..654c99778e 100644
--- a/django/utils/tzinfo.py
+++ b/django/utils/tzinfo.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import time
from datetime import timedelta, tzinfo
-from django.utils.encoding import smart_text, smart_str, DEFAULT_LOCALE_ENCODING
+from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
# Python's doc say: "A tzinfo subclass must have an __init__() method that can
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
@@ -53,7 +53,7 @@ class LocalTimezone(tzinfo):
self._tzname = self.tzname(dt)
def __repr__(self):
- return smart_str(self._tzname)
+ return force_str(self._tzname)
def __getinitargs__(self):
return self.__dt,
@@ -72,7 +72,7 @@ class LocalTimezone(tzinfo):
def tzname(self, dt):
try:
- return smart_text(time.tzname[self._isdst(dt)],
+ return force_text(time.tzname[self._isdst(dt)],
DEFAULT_LOCALE_ENCODING)
except UnicodeDecodeError:
return None
diff --git a/django/views/debug.py b/django/views/debug.py
index b275ef9e73..ed99d8dfe6 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -14,7 +14,7 @@ from django.template import Template, Context, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint
from django.utils.html import escape
from django.utils.importlib import import_module
-from django.utils.encoding import smart_text, smart_bytes
+from django.utils.encoding import force_bytes, smart_text
from django.utils import six
HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE')
@@ -440,7 +440,7 @@ def technical_404_response(request, exception):
'root_urlconf': settings.ROOT_URLCONF,
'request_path': request.path_info[1:], # Trim leading slash
'urlpatterns': tried,
- 'reason': smart_bytes(exception, errors='replace'),
+ 'reason': force_bytes(exception, errors='replace'),
'request': request,
'settings': get_safe_settings(),
})
diff --git a/django/views/generic/base.py b/django/views/generic/base.py
index 6554e898ca..e11412ba4d 100644
--- a/django/views/generic/base.py
+++ b/django/views/generic/base.py
@@ -18,6 +18,8 @@ class ContextMixin(object):
"""
def get_context_data(self, **kwargs):
+ if 'view' not in kwargs:
+ kwargs['view'] = self
return kwargs
@@ -140,11 +142,11 @@ class TemplateResponseMixin(object):
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
- A view that renders a template. This view is different from all the others
- insofar as it also passes ``kwargs`` as ``params`` to the template context.
+ A view that renders a template. This view will also pass into the context
+ any keyword arguments passed by the url conf.
"""
def get(self, request, *args, **kwargs):
- context = self.get_context_data(params=kwargs)
+ context = self.get_context_data(**kwargs)
return self.render_to_response(context)
diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py
index 08b7318738..d44246f0b7 100644
--- a/django/views/generic/dates.py
+++ b/django/views/generic/dates.py
@@ -327,6 +327,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
Abstract base class for date-based views displaying a list of objects.
"""
allow_empty = False
+ date_list_period = 'year'
def get(self, request, *args, **kwargs):
self.date_list, self.object_list, extra_context = self.get_dated_items()
@@ -370,13 +371,18 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
return qs
- def get_date_list(self, queryset, date_type):
+ def get_date_list_period(self):
+ return self.date_list_period
+
+ def get_date_list(self, queryset, date_type=None):
"""
Get a date list by calling `queryset.dates()`, checking along the way
for empty lists that aren't allowed.
"""
date_field = self.get_date_field()
allow_empty = self.get_allow_empty()
+ if date_type is None:
+ date_type = self.get_date_list_period()
date_list = queryset.dates(date_field, date_type)[::-1]
if date_list is not None and not date_list and not allow_empty:
@@ -400,7 +406,7 @@ class BaseArchiveIndexView(BaseDateListView):
Return (date_list, items, extra_context) for this request.
"""
qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field())
- date_list = self.get_date_list(qs, 'year')
+ date_list = self.get_date_list(qs)
if not date_list:
qs = qs.none()
@@ -419,6 +425,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
"""
List of objects published in a given year.
"""
+ date_list_period = 'month'
make_object_list = False
def get_dated_items(self):
@@ -438,7 +445,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
}
qs = self.get_dated_queryset(ordering='-%s' % date_field, **lookup_kwargs)
- date_list = self.get_date_list(qs, 'month')
+ date_list = self.get_date_list(qs)
if not self.get_make_object_list():
# We need this to be a queryset since parent classes introspect it
@@ -470,6 +477,8 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
"""
List of objects published in a given year.
"""
+ date_list_period = 'day'
+
def get_dated_items(self):
"""
Return (date_list, items, extra_context) for this request.
@@ -489,7 +498,7 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
}
qs = self.get_dated_queryset(**lookup_kwargs)
- date_list = self.get_date_list(qs, 'day')
+ date_list = self.get_date_list(qs)
return (date_list, qs, {
'month': date,
diff --git a/django/views/static.py b/django/views/static.py
index bcac9475e2..2ff22ce13f 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -61,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size):
- return HttpResponseNotModified(content_type=mimetype)
+ return HttpResponseNotModified()
with open(fullpath, 'rb') as f:
response = HttpResponse(f.read(), content_type=mimetype)
response["Last-Modified"] = http_date(statobj.st_mtime)
diff --git a/docs/conf.py b/docs/conf.py
index 39a280e464..433fd679a1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -190,11 +190,10 @@ modindex_common_prefix = ["django."]
# -- Options for LaTeX output --------------------------------------------------
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
+latex_elements = {
+ 'preamble': ('\\DeclareUnicodeCharacter{2264}{\\ensuremath{\\le}}'
+ '\\DeclareUnicodeCharacter{2265}{\\ensuremath{\\ge}}')
+}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, document class [howto/manual]).
@@ -218,9 +217,6 @@ latex_documents = [
# If true, show URL addresses after external links.
#latex_show_urls = False
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
# Documents to append as an appendix to all manuals.
#latex_appendices = []
diff --git a/docs/faq/general.txt b/docs/faq/general.txt
index 771af8a2cc..659a8c5ad9 100644
--- a/docs/faq/general.txt
+++ b/docs/faq/general.txt
@@ -185,6 +185,6 @@ would be happy to help you.
You might also be interested in posting a job to http://djangogigs.com/ .
If you want to find Django-capable people in your local area, try
-http://djangopeople.net/ .
+https://people.djangoproject.com/ .
.. _developers for hire page: https://code.djangoproject.com/wiki/DevelopersForHire
diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
index 054c831fad..5b27af82d6 100644
--- a/docs/howto/custom-template-tags.txt
+++ b/docs/howto/custom-template-tags.txt
@@ -189,7 +189,7 @@ passed around inside the template code:
They're commonly used for output that contains raw HTML that is intended
to be interpreted as-is on the client side.
- Internally, these strings are of type ``SafeString`` or ``SafeUnicode``.
+ Internally, these strings are of type ``SafeBytes`` or ``SafeText``.
They share a common base class of ``SafeData``, so you can test
for them using code like:
@@ -204,8 +204,8 @@ passed around inside the template code:
not. These strings are only escaped once, however, even if auto-escaping
applies.
- Internally, these strings are of type ``EscapeString`` or
- ``EscapeUnicode``. Generally you don't have to worry about these; they
+ Internally, these strings are of type ``EscapeBytes`` or
+ ``EscapeText``. Generally you don't have to worry about these; they
exist for the implementation of the :tfilter:`escape` filter.
Template filter code falls into one of two situations:
diff --git a/docs/howto/deployment/wsgi/gunicorn.txt b/docs/howto/deployment/wsgi/gunicorn.txt
index 13d4043e37..c4483291a3 100644
--- a/docs/howto/deployment/wsgi/gunicorn.txt
+++ b/docs/howto/deployment/wsgi/gunicorn.txt
@@ -13,7 +13,7 @@ There are two ways to use Gunicorn with Django. One is to have Gunicorn treat
Django as just another WSGI application. The second is to use Gunicorn's
special `integration with Django`_.
-.. _integration with Django: http://gunicorn.org/run.html#django-manage-py_
+.. _integration with Django: http://gunicorn.org/run.html#django-manage-py
Installing Gunicorn
===================
diff --git a/docs/index.txt b/docs/index.txt
index 011ecdb0bc..3f4e30385c 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -104,9 +104,12 @@ to know about views via the links below:
:doc:`Custom storage <howto/custom-file-storage>`
* **Class-based views:**
- :doc:`Overview<topics/class-based-views/index>` |
- :doc:`Built-in class-based views<ref/class-based-views/index>` |
- :doc:`Built-in view mixins<ref/class-based-views/mixins>`
+ :doc:`Overview <topics/class-based-views/index>` |
+ :doc:`Built-in display views <topics/class-based-views/generic-display>` |
+ :doc:`Built-in editing views <topics/class-based-views/generic-editing>` |
+ :doc:`Using mixins <topics/class-based-views/mixins>` |
+ :doc:`API reference <ref/class-based-views/index>` |
+ :doc:`Flattened index<ref/class-based-views/flattened-index>`
* **Advanced:**
:doc:`Generating CSV <howto/outputting-csv>` |
diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt
index 5567c26fa1..2faace99a5 100644
--- a/docs/internals/committers.txt
+++ b/docs/internals/committers.txt
@@ -77,7 +77,7 @@ record of being helpful on the mailing lists, and a proven desire to dedicate
serious time to Django. In return, they've been granted the coveted commit bit,
and have free rein to hack on all parts of Django.
-`Malcolm Tredinnick`_
+Malcolm Tredinnick
Malcolm originally wanted to be a mathematician, somehow ended up a software
developer. He's contributed to many Open Source projects, has served on the
board of the GNOME foundation, and will kick your ass at chess.
@@ -85,8 +85,6 @@ and have free rein to hack on all parts of Django.
When he's not busy being an International Man of Mystery, Malcolm lives in
Sydney, Australia.
-.. _malcolm tredinnick: http://www.pointy-stick.com/
-
`Russell Keith-Magee`_
Russell studied physics as an undergraduate, and studied neural networks for
his PhD. His first job was with a startup in the defense industry developing
diff --git a/docs/internals/contributing/bugs-and-features.txt b/docs/internals/contributing/bugs-and-features.txt
index 91a078cbc0..53b0e1007d 100644
--- a/docs/internals/contributing/bugs-and-features.txt
+++ b/docs/internals/contributing/bugs-and-features.txt
@@ -62,8 +62,6 @@ To understand the lifecycle of your ticket once you have created it, refer to
.. _django-updates: http://groups.google.com/group/django-updates
-.. _reporting-security-issues:
-
Reporting user interface bugs and features
------------------------------------------
diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt
index d15b2f43ae..6d4fb7eef1 100644
--- a/docs/intro/tutorial03.txt
+++ b/docs/intro/tutorial03.txt
@@ -550,5 +550,16 @@ with the :ttag:`url` template tag:
<li><a href="{% url 'polls.views.detail' poll.id %}">{{ poll.question }}</a></li>
+.. note::
+
+ If ``{% url 'polls.views.detail' poll.id %}`` (with quotes) doesn't work,
+ but ``{% url polls.views.detail poll.id %}`` (without quotes) does, that
+ means you're using a version of Django ≤ 1.4. In this case, add the
+ following declaration at the top of your template:
+
+ .. code-block:: html+django
+
+ {% load url from future %}
+
When you're comfortable with writing views, read :doc:`part 4 of this tutorial
</intro/tutorial04>` to learn about simple form processing and generic views.
diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt
index 5e0360c88f..3f82b44f46 100644
--- a/docs/ref/class-based-views/base.txt
+++ b/docs/ref/class-based-views/base.txt
@@ -8,6 +8,9 @@ themselves or inherited from. They may not provide all the capabilities
required for projects, in which case there are Mixins and Generic class-based
views.
+View
+----
+
.. class:: django.views.generic.base.View
The master class-based base view. All other class-based views inherit from
@@ -31,13 +34,13 @@ views.
**Example urls.py**::
from django.conf.urls import patterns, url
-
+
from myapp.views import MyView
-
+
urlpatterns = patterns('',
url(r'^mine/$', MyView.as_view(), name='my-view'),
)
-
+
**Methods**
.. method:: dispatch(request, *args, **kwargs)
@@ -61,13 +64,16 @@ views.
The default implementation returns ``HttpResponseNotAllowed`` with list
of allowed methods in plain text.
-
- .. note::
+
+ .. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.
+TemplateView
+------------
+
.. class:: django.views.generic.base.TemplateView
Renders a given template, passing it a ``{{ params }}`` template variable,
@@ -84,22 +90,22 @@ views.
1. :meth:`dispatch()`
2. :meth:`http_method_not_allowed()`
3. :meth:`get_context_data()`
-
+
**Example views.py**::
from django.views.generic.base import TemplateView
-
+
from articles.models import Article
class HomePageView(TemplateView):
template_name = "home.html"
-
+
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['latest_articles'] = Article.objects.all()[:5]
return context
-
+
**Example urls.py**::
from django.conf.urls import patterns, url
@@ -126,12 +132,15 @@ views.
* ``params``: The dictionary of keyword arguments captured from the URL
pattern that served the view.
- .. note::
+ .. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.
+RedirectView
+------------
+
.. class:: django.views.generic.base.RedirectView
Redirects to a given URL.
@@ -159,7 +168,7 @@ views.
from django.shortcuts import get_object_or_404
from django.views.generic.base import RedirectView
-
+
from articles.models import Article
class ArticleCounterRedirectView(RedirectView):
@@ -176,7 +185,7 @@ views.
from django.conf.urls import patterns, url
from django.views.generic.base import RedirectView
-
+
from article.views import ArticleCounterRedirectView
urlpatterns = patterns('',
@@ -217,7 +226,7 @@ views.
behavior they wish, as long as the method returns a redirect-ready URL
string.
- .. note::
+ .. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt
new file mode 100644
index 0000000000..cbce3690e2
--- /dev/null
+++ b/docs/ref/class-based-views/flattened-index.txt
@@ -0,0 +1,556 @@
+===========================================
+Class-based generic views - flattened index
+===========================================
+
+This index provides an alternate organization of the reference documentation
+for class-based views. For each view, the effective attributes and methods from
+the class tree are represented under that view. For the reference
+documentation organized by the class which defines the behavior, see
+:doc:`Class-based views</ref/class-based-views/index>`
+
+Simple generic views
+--------------------
+
+View
+~~~~
+.. class:: View()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.base.View.http_method_names`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+
+TemplateView
+~~~~~~~~~~~~
+.. class:: TemplateView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.base.TemplateView.get`
+* :meth:`~django.views.generic.base.TemplateView.get_context_data`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+RedirectView
+~~~~~~~~~~~~
+.. class:: RedirectView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.base.RedirectView.permanent`
+* :attr:`~django.views.generic.base.RedirectView.query_string`
+* :attr:`~django.views.generic.base.RedirectView.url`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.RedirectView.delete`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.base.RedirectView.get`
+* :meth:`~django.views.generic.base.RedirectView.get_redirect_url`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.base.RedirectView.options`
+* :meth:`~django.views.generic.base.RedirectView.post`
+* :meth:`~django.views.generic.base.RedirectView.put`
+
+Detail Views
+------------
+
+DetailView
+~~~~~~~~~~
+.. class:: DetailView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.detail.BaseDetailView.get`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+List Views
+----------
+
+ListView
+~~~~~~~~
+.. class:: ListView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.list.BaseListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+Editing views
+-------------
+
+FormView
+~~~~~~~~
+.. class:: FormView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.edit.FormMixin.form_invalid`
+* :meth:`~django.views.generic.edit.FormMixin.form_valid`
+* :meth:`~django.views.generic.edit.ProcessFormView.get`
+* :meth:`~django.views.generic.edit.FormMixin.get_context_data`
+* :meth:`~django.views.generic.edit.FormMixin.get_form`
+* :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.edit.ProcessFormView.post`
+* :meth:`~django.views.generic.edit.ProcessFormView.put`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+CreateView
+~~~~~~~~~~
+.. class:: CreateView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.edit.FormMixin.form_invalid`
+* :meth:`~django.views.generic.edit.FormMixin.form_valid`
+* :meth:`~django.views.generic.edit.ProcessFormView.get`
+* :meth:`~django.views.generic.edit.FormMixin.get_context_data`
+* :meth:`~django.views.generic.edit.FormMixin.get_form`
+* :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.edit.ProcessFormView.post`
+* :meth:`~django.views.generic.edit.ProcessFormView.put`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+UpdateView
+~~~~~~~~~~
+.. class:: UpdateView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.edit.FormMixin.form_invalid`
+* :meth:`~django.views.generic.edit.FormMixin.form_valid`
+* :meth:`~django.views.generic.edit.ProcessFormView.get`
+* :meth:`~django.views.generic.edit.FormMixin.get_context_data`
+* :meth:`~django.views.generic.edit.FormMixin.get_form`
+* :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.edit.ProcessFormView.post`
+* :meth:`~django.views.generic.edit.ProcessFormView.put`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+DeleteView
+~~~~~~~~~~
+.. class:: DeleteView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.edit.DeletionMixin.success_url` [:meth:`~django.views.generic.edit.DeletionMixin.get_success_url`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.edit.DeletionMixin.delete`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.detail.BaseDetailView.get`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.edit.DeletionMixin.post`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+Date-based views
+----------------
+
+ArchiveIndexView
+~~~~~~~~~~~~~~~~
+.. class:: ArchiveIndexView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.dates.BaseDateListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+YearArchiveView
+~~~~~~~~~~~~~~~
+.. class:: YearArchiveView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.dates.BaseYearArchiveView.make_object_list` [:meth:`~django.views.generic.dates.BaseYearArchiveView.get_make_object_list`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
+* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.dates.BaseDateListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+MonthArchiveView
+~~~~~~~~~~~~~~~~
+.. class:: MonthArchiveView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
+* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
+* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.dates.BaseDateListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset`
+* :meth:`~django.views.generic.dates.MonthMixin.get_next_month`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+WeekArchiveView
+~~~~~~~~~~~~~~~
+.. class:: WeekArchiveView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+* :attr:`~django.views.generic.dates.WeekMixin.week` [:meth:`~django.views.generic.dates.WeekMixin.get_week`]
+* :attr:`~django.views.generic.dates.WeekMixin.week_format` [:meth:`~django.views.generic.dates.WeekMixin.get_week_format`]
+* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
+* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.dates.BaseDateListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+DayArchiveView
+~~~~~~~~~~~~~~
+.. class:: DayArchiveView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`]
+* :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
+* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
+* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.dates.BaseDateListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset`
+* :meth:`~django.views.generic.dates.DayMixin.get_next_day`
+* :meth:`~django.views.generic.dates.MonthMixin.get_next_month`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.dates.DayMixin.get_previous_day`
+* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+TodayArchiveView
+~~~~~~~~~~~~~~~~
+.. class:: TodayArchiveView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`]
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`]
+* :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.model`
+* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
+* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`]
+* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class`
+* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix`
+* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
+* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.dates.BaseDateListView.get`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items`
+* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset`
+* :meth:`~django.views.generic.dates.DayMixin.get_next_day`
+* :meth:`~django.views.generic.dates.MonthMixin.get_next_month`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator`
+* :meth:`~django.views.generic.dates.DayMixin.get_previous_day`
+* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
+
+DateDetailView
+~~~~~~~~~~~~~~
+.. class:: DateDetailView()
+
+**Attributes** (with optional accessor):
+
+* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
+* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`]
+* :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`]
+* :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`]
+* :attr:`~django.views.generic.base.View.http_method_names`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
+* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`]
+* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
+* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class`
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
+* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg`
+* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field`
+* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
+* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`]
+* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`]
+
+**Methods**
+
+* :meth:`~django.views.generic.base.View.as_view`
+* :meth:`~django.views.generic.base.View.dispatch`
+* :meth:`~django.views.generic.detail.BaseDetailView.get`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data`
+* :meth:`~django.views.generic.dates.DayMixin.get_next_day`
+* :meth:`~django.views.generic.dates.MonthMixin.get_next_month`
+* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object`
+* :meth:`~django.views.generic.dates.DayMixin.get_previous_day`
+* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month`
+* :meth:`~django.views.generic.base.View.head`
+* :meth:`~django.views.generic.base.View.http_method_not_allowed`
+* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`
diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt
index 69a98df77b..12776cbb94 100644
--- a/docs/ref/class-based-views/generic-date-based.txt
+++ b/docs/ref/class-based-views/generic-date-based.txt
@@ -5,6 +5,9 @@ Generic date views
Date-based generic views (in the module :mod:`django.views.generic.dates`)
are views for displaying drilldown pages for date-based data.
+ArchiveIndexView
+----------------
+
.. class:: django.views.generic.dates.ArchiveIndexView
A top-level index page showing the "latest" objects, by date. Objects with
@@ -21,11 +24,17 @@ are views for displaying drilldown pages for date-based data.
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
-
+
**Notes**
* Uses a default ``context_object_name`` of ``latest``.
* Uses a default ``template_name_suffix`` of ``_archive``.
+ * Defaults to providing ``date_list`` by year, but this can be altered to
+ month or day using the attribute ``date_list_period``. This also applies
+ to all subclass views.
+
+YearArchiveView
+---------------
.. class:: django.views.generic.dates.YearArchiveView
@@ -86,6 +95,9 @@ are views for displaying drilldown pages for date-based data.
* Uses a default ``template_name_suffix`` of ``_archive_year``.
+MonthArchiveView
+----------------
+
.. class:: django.views.generic.dates.MonthArchiveView
A monthly archive page showing all objects in a given month. Objects with a
@@ -134,6 +146,9 @@ are views for displaying drilldown pages for date-based data.
* Uses a default ``template_name_suffix`` of ``_archive_month``.
+WeekArchiveView
+---------------
+
.. class:: django.views.generic.dates.WeekArchiveView
A weekly archive page showing all objects in a given week. Objects with a
@@ -175,6 +190,9 @@ are views for displaying drilldown pages for date-based data.
* Uses a default ``template_name_suffix`` of ``_archive_week``.
+DayArchiveView
+--------------
+
.. class:: django.views.generic.dates.DayArchiveView
A day archive page showing all objects in a given day. Days in the future
@@ -225,6 +243,9 @@ are views for displaying drilldown pages for date-based data.
* Uses a default ``template_name_suffix`` of ``_archive_day``.
+TodayArchiveView
+----------------
+
.. class:: django.views.generic.dates.TodayArchiveView
A day archive page showing all objects for *today*. This is exactly the
@@ -246,6 +267,10 @@ are views for displaying drilldown pages for date-based data.
* :class:`django.views.generic.dates.DateMixin`
* :class:`django.views.generic.base.View`
+
+DateDetailView
+--------------
+
.. class:: django.views.generic.dates.DateDetailView
A page representing an individual object. If the object has a date value in
diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt
index bbf0d4f05a..ef3bc179ee 100644
--- a/docs/ref/class-based-views/generic-display.txt
+++ b/docs/ref/class-based-views/generic-display.txt
@@ -5,6 +5,9 @@ Generic display views
The two following generic class-based views are designed to display data. On
many projects they are typically the most commonly used views.
+DetailView
+----------
+
.. class:: django.views.generic.detail.DetailView
While this view is executing, ``self.object`` will contain the object that
@@ -39,7 +42,7 @@ many projects they are typically the most commonly used views.
from articles.models import Article
class ArticleDetailView(DetailView):
-
+
model = Article
def get_context_data(self, **kwargs):
@@ -55,7 +58,10 @@ many projects they are typically the most commonly used views.
urlpatterns = patterns('',
url(r'^(?P<slug>[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'),
- )
+ )
+
+ListView
+--------
.. class:: django.views.generic.list.ListView
diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt
index a65a59bc8b..2fac06ee02 100644
--- a/docs/ref/class-based-views/generic-editing.txt
+++ b/docs/ref/class-based-views/generic-editing.txt
@@ -2,7 +2,7 @@
Generic editing views
=====================
-The following views are described on this page and provide a foundation for
+The following views are described on this page and provide a foundation for
editing content:
* :class:`django.views.generic.edit.FormView`
@@ -13,7 +13,7 @@ editing content:
.. note::
Some of the examples on this page assume that a model titled 'Author'
- has been defined. For these cases we assume the following has been defined
+ has been defined. For these cases we assume the following has been defined
in `myapp/models.py`::
from django import models
@@ -25,6 +25,9 @@ editing content:
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
+FormView
+--------
+
.. class:: django.views.generic.edit.FormView
A view that displays a form. On error, redisplays the form with validation
@@ -69,6 +72,8 @@ editing content:
form.send_email()
return super(ContactView, self).form_valid(form)
+CreateView
+----------
.. class:: django.views.generic.edit.CreateView
@@ -94,7 +99,7 @@ editing content:
.. attribute:: template_name_suffix
The CreateView page displayed to a GET request uses a
- ``template_name_suffix`` of ``'_form.html'``. For
+ ``template_name_suffix`` of ``'_form.html'``. For
example, changing this attribute to ``'_create_form.html'`` for a view
creating objects for the the example `Author` model would cause the the
default `template_name` to be ``'myapp/author_create_form.html'``.
@@ -107,6 +112,9 @@ editing content:
class AuthorCreate(CreateView):
model = Author
+UpdateView
+----------
+
.. class:: django.views.generic.edit.UpdateView
A view that displays a form for editing an existing object, redisplaying
@@ -133,10 +141,10 @@ editing content:
.. attribute:: template_name_suffix
The UpdateView page displayed to a GET request uses a
- ``template_name_suffix`` of ``'_form.html'``. For
+ ``template_name_suffix`` of ``'_form.html'``. For
example, changing this attribute to ``'_update_form.html'`` for a view
updating objects for the the example `Author` model would cause the the
- default `template_name` to be ``'myapp/author_update_form.html'``.
+ default `template_name` to be ``'myapp/author_update_form.html'``.
**Example views.py**::
@@ -146,6 +154,9 @@ editing content:
class AuthorUpdate(UpdateView):
model = Author
+DeleteView
+----------
+
.. class:: django.views.generic.edit.DeleteView
A view that displays a confirmation page and deletes an existing object.
@@ -171,10 +182,10 @@ editing content:
.. attribute:: template_name_suffix
The DeleteView page displayed to a GET request uses a
- ``template_name_suffix`` of ``'_confirm_delete.html'``. For
+ ``template_name_suffix`` of ``'_confirm_delete.html'``. For
example, changing this attribute to ``'_check_delete.html'`` for a view
deleting objects for the the example `Author` model would cause the the
- default `template_name` to be ``'myapp/author_check_delete.html'``.
+ default `template_name` to be ``'myapp/author_check_delete.html'``.
**Example views.py**::
@@ -185,4 +196,4 @@ editing content:
class AuthorDelete(DeleteView):
model = Author
- success_url = reverse_lazy('author-list')
+ success_url = reverse_lazy('author-list')
diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt
index f2271d2506..f0e7bbc6c1 100644
--- a/docs/ref/class-based-views/index.txt
+++ b/docs/ref/class-based-views/index.txt
@@ -6,13 +6,14 @@ Class-based views API reference. For introductory material, see
:doc:`/topics/class-based-views/index`.
.. toctree::
- :maxdepth: 1
+ :maxdepth: 3
base
generic-display
generic-editing
generic-date-based
mixins
+ flattened-index
Specification
-------------
diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt
index a65471e68d..6bf6f10b5d 100644
--- a/docs/ref/class-based-views/mixins-date-based.txt
+++ b/docs/ref/class-based-views/mixins-date-based.txt
@@ -3,6 +3,9 @@ Date-based mixins
=================
+YearMixin
+---------
+
.. class:: django.views.generic.dates.YearMixin
A mixin that can be used to retrieve and provide parsing information for a
@@ -36,6 +39,9 @@ Date-based mixins
Raises a 404 if no valid year specification can be found.
+MonthMixin
+----------
+
.. class:: django.views.generic.dates.MonthMixin
A mixin that can be used to retrieve and provide parsing information for a
@@ -82,6 +88,9 @@ Date-based mixins
date provided. If ``allow_empty = False``, returns the previous month
that contained data.
+DayMixin
+--------
+
.. class:: django.views.generic.dates.DayMixin
A mixin that can be used to retrieve and provide parsing information for a
@@ -127,6 +136,9 @@ Date-based mixins
Returns a date object containing the previous day. If
``allow_empty = False``, returns the previous day that contained data.
+WeekMixin
+---------
+
.. class:: django.views.generic.dates.WeekMixin
A mixin that can be used to retrieve and provide parsing information for a
@@ -161,6 +173,9 @@ Date-based mixins
Raises a 404 if no valid week specification can be found.
+DateMixin
+---------
+
.. class:: django.views.generic.dates.DateMixin
A mixin class providing common behavior for all date-based views.
@@ -204,6 +219,9 @@ Date-based mixins
is greater than the current date/time. Returns
:attr:`DateMixin.allow_future` by default.
+BaseDateListView
+----------------
+
.. class:: django.views.generic.dates.BaseDateListView
A base class that provides common behavior for all date-based views. There
diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt
index 89610889db..95dd24f442 100644
--- a/docs/ref/class-based-views/mixins-editing.txt
+++ b/docs/ref/class-based-views/mixins-editing.txt
@@ -14,6 +14,9 @@ The following mixins are used to construct Django's editing views:
Examples of how these are combined into editing views can be found at
the documentation on ``Generic editing views``.
+FormMixin
+---------
+
.. class:: django.views.generic.edit.FormMixin
A mixin class that provides facilities for creating and displaying forms.
@@ -90,6 +93,9 @@ The following mixins are used to construct Django's editing views:
:meth:`~django.views.generic.FormMixin.form_invalid`.
+ModelFormMixin
+--------------
+
.. class:: django.views.generic.edit.ModelFormMixin
A form mixin that works on ModelForms, rather than a standalone form.
@@ -147,12 +153,16 @@ The following mixins are used to construct Django's editing views:
.. method:: form_invalid()
Renders a response, providing the invalid form as context.
-
+
+
+ProcessFormView
+---------------
+
.. class:: django.views.generic.edit.ProcessFormView
A mixin that provides basic HTTP GET and POST workflow.
- .. note::
+ .. note::
This is named 'ProcessFormView' and inherits directly from
:class:`django.views.generic.base.View`, but breaks if used
diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt
index b414355c6b..8bc613b887 100644
--- a/docs/ref/class-based-views/mixins-multiple-object.txt
+++ b/docs/ref/class-based-views/mixins-multiple-object.txt
@@ -2,6 +2,9 @@
Multiple object mixins
======================
+MultipleObjectMixin
+-------------------
+
.. class:: django.views.generic.list.MultipleObjectMixin
A mixin that can be used to display a list of objects.
@@ -148,6 +151,9 @@ Multiple object mixins
this context variable will be ``None``.
+MultipleObjectTemplateResponseMixin
+-----------------------------------
+
.. class:: django.views.generic.list.MultipleObjectTemplateResponseMixin
A mixin class that performs template-based response rendering for views
diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt
index 33d0db3134..61fc945cd3 100644
--- a/docs/ref/class-based-views/mixins-simple.txt
+++ b/docs/ref/class-based-views/mixins-simple.txt
@@ -2,6 +2,9 @@
Simple mixins
=============
+ContextMixin
+------------
+
.. class:: django.views.generic.base.ContextMixin
.. versionadded:: 1.5
@@ -14,8 +17,23 @@ Simple mixins
.. method:: get_context_data(**kwargs)
- Returns a dictionary representing the template context. The
- keyword arguments provided will make up the returned context.
+ Returns a dictionary representing the template context. The keyword
+ arguments provided will make up the returned context.
+
+ The template context of all class-based generic views include a
+ ``view`` variable that points to the ``View`` instance.
+
+ .. admonition:: Use ``alters_data`` where appropriate
+
+ Note that having the view instance in the template context may
+ expose potentially hazardous methods to template authors. To
+ prevent methods like this from being called in the template, set
+ ``alters_data=True`` on those methods. For more information, read
+ the documentation on :ref:`rendering a template context
+ <alters-data-description>`.
+
+TemplateResponseMixin
+---------------------
.. class:: django.views.generic.base.TemplateResponseMixin
diff --git a/docs/ref/class-based-views/mixins-single-object.txt b/docs/ref/class-based-views/mixins-single-object.txt
index a31608dbd4..77f52b96c6 100644
--- a/docs/ref/class-based-views/mixins-single-object.txt
+++ b/docs/ref/class-based-views/mixins-single-object.txt
@@ -2,6 +2,9 @@
Single object mixins
====================
+SingleObjectMixin
+-----------------
+
.. class:: django.views.generic.detail.SingleObjectMixin
Provides a mechanism for looking up an object associated with the
@@ -86,6 +89,9 @@ Single object mixins
``context_object_name`` is specified, that variable will also be
set in the context, with the same value as ``object``.
+SingleObjectTemplateResponseMixin
+---------------------------------
+
.. class:: django.views.generic.detail.SingleObjectTemplateResponseMixin
A mixin class that performs template-based response rendering for views
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 4d39981a4d..66a5a2cc4f 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -561,8 +561,6 @@ subclass::
.. attribute:: ModelAdmin.list_filter
- .. versionchanged:: 1.4
-
Set ``list_filter`` to activate filters in the right sidebar of the change
list page of the admin, as illustrated in the following screenshot:
@@ -586,6 +584,8 @@ subclass::
class PersonAdmin(UserAdmin):
list_filter = ('company__name',)
+ .. versionadded:: 1.4
+
* a class inheriting from :mod:`django.contrib.admin.SimpleListFilter`,
which you need to provide the ``title`` and ``parameter_name``
attributes to and override the ``lookups`` and ``queryset`` methods,
@@ -623,7 +623,7 @@ subclass::
provided in the query string and retrievable via
`self.value()`.
"""
- # Compare the requested value (either '80s' or 'other')
+ # Compare the requested value (either '80s' or '90s')
# to decide how to filter the queryset.
if self.value() == '80s':
return queryset.filter(birthday__gte=date(1980, 1, 1),
@@ -671,6 +671,8 @@ subclass::
birthday__lte=date(1999, 12, 31)).exists():
yield ('90s', _('in the nineties'))
+ .. versionadded:: 1.4
+
* a tuple, where the first element is a field name and the second
element is a class inheriting from
:mod:`django.contrib.admin.FieldListFilter`, for example::
diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt
index af937e036e..4b1dd96280 100644
--- a/docs/ref/contrib/comments/index.txt
+++ b/docs/ref/contrib/comments/index.txt
@@ -158,11 +158,13 @@ For example::
.. warning::
- There's a known bug in Safari/Webkit which causes the named anchor to be
+ There's a `known bug`_ in Safari/Webkit which causes the named anchor to be
forgotten following a redirect. The practical impact for comments is that
the Safari/webkit browsers will arrive at the correct page but will not
scroll to the named anchor.
+.. _`known bug`: https://bugs.webkit.org/show_bug.cgi?id=24175
+
.. templatetag:: get_comment_count
Counting comments
diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt
index f25cb31e4b..8d352ff8b2 100644
--- a/docs/ref/contrib/csrf.txt
+++ b/docs/ref/contrib/csrf.txt
@@ -84,47 +84,94 @@ AJAX
While the above method can be used for AJAX POST requests, it has some
inconveniences: you have to remember to pass the CSRF token in as POST data with
every POST request. For this reason, there is an alternative method: on each
-XMLHttpRequest, set a custom `X-CSRFToken` header to the value of the CSRF
+XMLHttpRequest, set a custom ``X-CSRFToken`` header to the value of the CSRF
token. This is often easier, because many javascript frameworks provide hooks
-that allow headers to be set on every request. In jQuery, you can use the
-``ajaxSend`` event as follows:
+that allow headers to be set on every request.
+
+As a first step, you must get the CSRF token itself. The recommended source for
+the token is the ``csrftoken`` cookie, which will be set if you've enabled CSRF
+protection for your views as outlined above.
+
+.. note::
+
+ The CSRF token cookie is named ``csrftoken`` by default, but you can control
+ the cookie name via the :setting:`CSRF_COOKIE_NAME` setting.
+
+Acquiring the token is straightforward:
.. code-block:: javascript
- jQuery(document).ajaxSend(function(event, xhr, settings) {
- function getCookie(name) {
- var cookieValue = null;
- if (document.cookie && document.cookie != '') {
- var cookies = document.cookie.split(';');
- for (var i = 0; i < cookies.length; i++) {
- var cookie = jQuery.trim(cookies[i]);
- // Does this cookie string begin with the name we want?
- if (cookie.substring(0, name.length + 1) == (name + '=')) {
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
- break;
- }
+ // using jQuery
+ function getCookie(name) {
+ var cookieValue = null;
+ if (document.cookie && document.cookie != '') {
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = jQuery.trim(cookies[i]);
+ // Does this cookie string begin with the name we want?
+ if (cookie.substring(0, name.length + 1) == (name + '=')) {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
}
}
- return cookieValue;
- }
- function sameOrigin(url) {
- // url could be relative or scheme relative or absolute
- var host = document.location.host; // host + port
- var protocol = document.location.protocol;
- var sr_origin = '//' + host;
- var origin = protocol + sr_origin;
- // Allow absolute or scheme relative URLs to same origin
- return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
- (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
- // or any other URL that isn't scheme relative or absolute i.e relative.
- !(/^(\/\/|http:|https:).*/.test(url));
- }
- function safeMethod(method) {
- return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
+ return cookieValue;
+ }
+ var csrftoken = getCookie('csrftoken');
+
+The above code could be simplified by using the `jQuery cookie plugin
+<http://plugins.jquery.com/project/Cookie>`_ to replace ``getCookie``:
+
+.. code-block:: javascript
+
+ var csrftoken = $.cookie('csrftoken');
+
+.. note::
+
+ The CSRF token is also present in the DOM, but only if explicitly included
+ using :ttag:`csrf_token` in a template. The cookie contains the canonical
+ token; the ``CsrfViewMiddleware`` will prefer the cookie to the token in
+ the DOM. Regardless, you're guaranteed to have the cookie if the token is
+ present in the DOM, so you should use the cookie!
- if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
- xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
+.. warning::
+
+ If your view is not rendering a template containing the :ttag:`csrf_token`
+ template tag, Django might not set the CSRF token cookie. This is common in
+ cases where forms are dynamically added to the page. To address this case,
+ Django provides a view decorator which forces setting of the cookie:
+ :func:`~django.views.decorators.csrf.ensure_csrf_cookie`.
+
+Finally, you'll have to actually set the header on your AJAX request, while
+protecting the CSRF token from being sent to other domains.
+
+.. code-block:: javascript
+
+ function csrfSafeMethod(method) {
+ // these HTTP methods do not require CSRF protection
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+ }
+ function sameOrigin(url) {
+ // test that a given url is a same-origin URL
+ // url could be relative or scheme relative or absolute
+ var host = document.location.host; // host + port
+ var protocol = document.location.protocol;
+ var sr_origin = '//' + host;
+ var origin = protocol + sr_origin;
+ // Allow absolute or scheme relative URLs to same origin
+ return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
+ (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
+ // or any other URL that isn't scheme relative or absolute i.e relative.
+ !(/^(\/\/|http:|https:).*/.test(url));
+ }
+ $.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
+ // Send the token to same-origin, relative URLs only.
+ // Send the token only if the method warrants CSRF protection
+ // Using the CSRFToken value acquired earlier
+ xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ }
}
});
@@ -133,18 +180,32 @@ that allow headers to be set on every request. In jQuery, you can use the
Due to a bug introduced in jQuery 1.5, the example above will not work
correctly on that version. Make sure you are running at least jQuery 1.5.1.
-Adding this to a javascript file that is included on your site will ensure that
-AJAX POST requests that are made via jQuery will not be caught by the CSRF
-protection.
+You can use `settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in
+jQuery 1.5 and newer in order to replace the `sameOrigin` logic above:
-The above code could be simplified by using the `jQuery cookie plugin
-<http://plugins.jquery.com/project/Cookie>`_ to replace ``getCookie``, and
-`settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in jQuery 1.5 and
-later to replace ``sameOrigin``.
+.. code-block:: javascript
+
+ function csrfSafeMethod(method) {
+ // these HTTP methods do not require CSRF protection
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+ }
+ $.ajaxSetup({
+ crossDomain: false, // obviates need for sameOrigin test
+ beforeSend: function(xhr, settings) {
+ if (!csrfSafeMethod(settings.type)) {
+ xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ }
+ }
+ });
+
+.. note::
+
+ In a `security release blogpost`_, a simpler "same origin test" example
+ was provided which only checked for a relative URL. The ``sameOrigin``
+ test above supersedes that example—it works for edge cases like
+ scheme-relative or absolute URLs for the same domain.
-In addition, if the CSRF cookie has not been sent to the client by use of
-:ttag:`csrf_token`, you may need to ensure the client receives the cookie by
-using :func:`~django.views.decorators.csrf.ensure_csrf_cookie`.
+.. _security release blogpost: https://www.djangoproject.com/weblog/2011/feb/08/security/
Other template engines
----------------------
diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt
index 6b5773b5a4..3de449708f 100644
--- a/docs/ref/contrib/flatpages.txt
+++ b/docs/ref/contrib/flatpages.txt
@@ -42,6 +42,16 @@ To install the flatpages app, follow these steps:
2. Add ``'django.contrib.flatpages'`` to your :setting:`INSTALLED_APPS`
setting.
+Then either:
+
+3. Add an entry in your URLconf. For example::
+
+ urlpatterns = patterns('',
+ ('^pages/', include('django.contrib.flatpages.urls')),
+ )
+
+or:
+
3. Add ``'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'``
to your :setting:`MIDDLEWARE_CLASSES` setting.
@@ -57,8 +67,38 @@ and ``django_flatpage_sites``. ``django_flatpage`` is a simple lookup table
that simply maps a URL to a title and bunch of text content.
``django_flatpage_sites`` associates a flatpage with a site.
+Using the URLconf
+-----------------
+
+There are several ways to include the flat pages in your URLconf. You can
+dedicate a particular path to flat pages::
+
+ urlpatterns = patterns('',
+ ('^pages/', include('django.contrib.flatpages.urls')),
+ )
+
+You can also set it up as a "catchall" pattern. In this case, it is important
+to place the pattern at the end of the other urlpatterns::
+
+ # Your other patterns here
+ urlpatterns += patterns('django.contrib.flatpages.views',
+ (r'^(?P<url>.*)$', 'flatpage'),
+ )
+
+Another common setup is to use flat pages for a limited set of known pages and
+to hard code the urls, so you can reference them with the :ttag:`url` template
+tag::
+
+ urlpatterns += patterns('django.contrib.flatpages.views',
+ url(r'^about-us/$', 'flatpage', {'url': '/about-us/'}, name='about'),
+ url(r'^license/$', 'flatpage', {'url': '/license/'}, name='license'),
+ )
+
+Using the middleware
+--------------------
+
The :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`
-does all of the work.
+can do all of the work.
.. class:: FlatpageFallbackMiddleware
@@ -255,4 +295,3 @@ For example:
{% get_flatpages '/about/' as about_pages %}
{% get_flatpages about_prefix as about_pages %}
{% get_flatpages '/about/' for someuser as about_pages %}
-
diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt
index 7d229a5d66..b8e585a4d2 100644
--- a/docs/ref/contrib/formtools/form-wizard.txt
+++ b/docs/ref/contrib/formtools/form-wizard.txt
@@ -86,8 +86,8 @@ the message itself. Here's what the :file:`forms.py` might look like::
section :ref:`Handling files <wizard-files>` below to learn more about
what to do.
-Creating a ``WizardView`` class
--------------------------------
+Creating a ``WizardView`` subclass
+----------------------------------
The next step is to create a
:class:`django.contrib.formtools.wizard.views.WizardView` subclass. You can
@@ -528,7 +528,7 @@ We define our wizard in a ``views.py``::
We need to add the ``ContactWizard`` to our ``urls.py`` file::
- from django.conf.urls import pattern
+ from django.conf.urls import patterns
from myapp.forms import ContactForm1, ContactForm2
from myapp.views import ContactWizard, show_message_form_condition
diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt
index 72bd72a6f8..3e952c173b 100644
--- a/docs/ref/contrib/gis/install.txt
+++ b/docs/ref/contrib/gis/install.txt
@@ -60,14 +60,14 @@ The geospatial libraries required for a GeoDjango installation depends
on the spatial database used. The following lists the library requirements,
supported versions, and any notes for each of the supported database backends:
-================== ============================== ================== ==========================================================
+================== ============================== ================== =========================================
Database Library Requirements Supported Versions Notes
-================== ============================== ================== ==========================================================
+================== ============================== ================== =========================================
PostgreSQL GEOS, PROJ.4, PostGIS 8.1+ Requires PostGIS.
MySQL GEOS 5.x Not OGC-compliant; limited functionality.
Oracle GEOS 10.2, 11 XE not supported; not tested with 9.
-SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires SpatiaLite 2.3+, pysqlite2 2.5+, and Django 1.1.
-================== ============================== ================== ==========================================================
+SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires SpatiaLite 2.3+, pysqlite2 2.5+
+================== ============================== ================== =========================================
.. _geospatial_libs:
@@ -140,7 +140,7 @@ internal geometry representation used by GeoDjango (it's behind the "lazy"
geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``)
directly from Python using ctypes.
-First, download GEOS 3.2 from the refractions Web site and untar the source
+First, download GEOS 3.3.0 from the refractions Web site and untar the source
archive::
$ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2
@@ -421,7 +421,7 @@ After SQLite has been built with the R*Tree module enabled, get the latest
SpatiaLite library source and tools bundle from the `download page`__::
$ wget http://www.gaia-gis.it/gaia-sins/libspatialite-sources/libspatialite-amalgamation-2.3.1.tar.gz
- $ wget http://www.gaia-gis.it/gaia-sins/libspatialite-sources/spatialite-tools-2.3.1.tar.gz
+ $ wget http://www.gaia-gis.it/gaia-sins/spatialite-tools-sources/spatialite-tools-2.3.1.tar.gz
$ tar xzf libspatialite-amalgamation-2.3.1.tar.gz
$ tar xzf spatialite-tools-2.3.1.tar.gz
@@ -467,8 +467,8 @@ pysqlite2
^^^^^^^^^
Because SpatiaLite must be loaded as an external extension, it requires the
-``enable_load_extension`` method, which is only available in versions 2.5+.
-Thus, download pysqlite2 2.6, and untar::
+``enable_load_extension`` method, which is only available in versions 2.5+ of
+pysqlite2. Thus, download pysqlite2 2.6, and untar::
$ wget http://pysqlite.googlecode.com/files/pysqlite-2.6.0.tar.gz
$ tar xzf pysqlite-2.6.0.tar.gz
@@ -582,8 +582,8 @@ Creating a spatial database for SpatiaLite
After you've installed SpatiaLite, you'll need to create a number of spatial
metadata tables in your database in order to perform spatial queries.
-If you're using SpatiaLite 3.0 or newer, use the ``spatialite`` utility to
-call the ``InitSpatiaMetaData()`` function, like this::
+If you're using SpatiaLite 2.4 or newer, use the ``spatialite`` utility to
+call the ``InitSpatialMetaData()`` function, like this::
$ spatialite geodjango.db "SELECT InitSpatialMetaData();"
the SPATIAL_REF_SYS table already contains some row(s)
@@ -593,19 +593,17 @@ call the ``InitSpatiaMetaData()`` function, like this::
You can safely ignore the error messages shown. When you've done this, you can
skip the rest of this section.
-If you're using a version of SpatiaLite older than 3.0, you'll need to download
-a database-initialization file and execute its SQL queries in your database.
+If you're using SpatiaLite 2.3, you'll need to download a
+database-initialization file and execute its SQL queries in your database.
-First, get it from the appropriate SpatiaLite Resources page (
-http://www.gaia-gis.it/spatialite-2.3.1/resources.html for 2.3 or
-http://www.gaia-gis.it/spatialite-2.4.0/ for 2.4)::
+First, get it from the `SpatiaLite Resources`__ page::
$ wget http://www.gaia-gis.it/spatialite-2.3.1/init_spatialite-2.3.sql.gz
$ gunzip init_spatialite-2.3.sql.gz
Then, use the ``spatialite`` command to initialize a spatial database::
- $ spatialite geodjango.db < init_spatialite-2.X.sql
+ $ spatialite geodjango.db < init_spatialite-2.3.sql
.. note::
@@ -613,6 +611,8 @@ Then, use the ``spatialite`` command to initialize a spatial database::
you want to use. Use the same in the :setting:`DATABASES` ``"name"`` key
inside your ``settings.py``.
+__ http://www.gaia-gis.it/spatialite-2.3.1/resources.html
+
Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS`
-------------------------------------------------------
@@ -643,10 +643,6 @@ Invoke the Django shell from your project and execute the
>>> from django.contrib.gis.utils import add_srs_entry
>>> add_srs_entry(900913)
-.. note::
-
- In Django 1.1 the name of this function is ``add_postgis_srs``.
-
This adds an entry for the 900913 SRID to the ``spatial_ref_sys`` (or equivalent)
table, making it possible for the spatial database to transform coordinates in
this projection. You only need to execute this command *once* per spatial database.
@@ -824,8 +820,10 @@ Download the framework packages for:
* GDAL
Install the packages in the order they are listed above, as the GDAL and SQLite
-packages require the packages listed before them. Afterwards, you can also
-install the KyngChaos binary packages for `PostgreSQL and PostGIS`__.
+packages require the packages listed before them.
+
+Afterwards, you can also install the KyngChaos binary packages for `PostgreSQL
+and PostGIS`__.
After installing the binary packages, you'll want to add the following to
your ``.profile`` to be able to run the package programs from the command-line::
diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt
index 78663b967c..d12c884a1b 100644
--- a/docs/ref/contrib/gis/testing.txt
+++ b/docs/ref/contrib/gis/testing.txt
@@ -119,9 +119,10 @@ Settings
``SPATIALITE_SQL``
^^^^^^^^^^^^^^^^^^
-Only relevant when using a SpatiaLite version older than 3.0.
+Only relevant when using a SpatiaLite version 2.3.
-By default, the GeoDjango test runner looks for the SpatiaLite SQL in the
+By default, the GeoDjango test runner looks for the :ref:`file containing the
+SpatiaLite dababase-initialization SQL code <create_spatialite_db>` in the
same directory where it was invoked (by default the same directory where
``manage.py`` is located). To use a different location, add the following to
your settings::
diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt
index 3a63493137..15863aee7b 100644
--- a/docs/ref/contrib/gis/tutorial.txt
+++ b/docs/ref/contrib/gis/tutorial.txt
@@ -671,6 +671,17 @@ of abstraction::
__ http://spatialreference.org/ref/epsg/32140/
+.. admonition:: Raw queries
+
+ When using :doc:`raw queries </topics/db/sql>`, you should generally wrap
+ your geometry fields with the ``asText()`` SQL function so as the field
+ value will be recognized by GEOS::
+
+ City.objects.raw('SELECT id, name, asText(point) from myapp_city')
+
+ This is not absolutely required by PostGIS, but generally you should only
+ use raw queries when you know exactly what you are doing.
+
Lazy Geometries
---------------
Geometries come to GeoDjango in a standardized textual representation. Upon
diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
index 92b5665bea..3e256e9d9e 100644
--- a/docs/ref/databases.txt
+++ b/docs/ref/databases.txt
@@ -56,10 +56,10 @@ will do some additional queries to set these parameters.
Transaction handling
---------------------
-:doc:`By default </topics/db/transactions>`, Django starts a transaction when a
-database connection is first used and commits the result at the end of the
-request/response handling. The PostgreSQL backends normally operate the same
-as any other Django backend in this respect.
+:doc:`By default </topics/db/transactions>`, Django runs with an open
+transaction which it commits automatically when any built-in, data-altering
+model function is called. The PostgreSQL backends normally operate the same as
+any other Django backend in this respect.
.. _postgresql-autocommit-mode:
diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt
index 99547f1c9e..ada614df45 100644
--- a/docs/ref/files/file.txt
+++ b/docs/ref/files/file.txt
@@ -91,14 +91,18 @@ The ``ContentFile`` Class
.. class:: ContentFile(File)
The ``ContentFile`` class inherits from :class:`~django.core.files.File`,
- but unlike :class:`~django.core.files.File` it operates on string content,
- rather than an actual file. For example::
+ but unlike :class:`~django.core.files.File` it operates on string content
+ (bytes also supported), rather than an actual file. For example::
from __future__ import unicode_literals
from django.core.files.base import ContentFile
- f1 = ContentFile(b"my string content")
- f2 = ContentFile("my unicode content encoded as UTF-8".encode('UTF-8'))
+ f1 = ContentFile("esta sentencia está en español")
+ f2 = ContentFile(b"these are bytes")
+
+ .. versionchanged:: 1.5
+
+ ContentFile also accepts Unicode strings.
.. currentmodule:: django.core.files.images
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index a43163c5e9..275c696230 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -70,8 +70,8 @@ If ``True``, the field is allowed to be blank. Default is ``False``.
Note that this is different than :attr:`~Field.null`. :attr:`~Field.null` is
purely database-related, whereas :attr:`~Field.blank` is validation-related. If
-a field has ``blank=True``, validation on Django's admin site will allow entry
-of an empty value. If a field has ``blank=False``, the field will be required.
+a field has ``blank=True``, form validation will allow entry of an empty value.
+If a field has ``blank=False``, the field will be required.
.. _field-choices:
@@ -81,14 +81,11 @@ of an empty value. If a field has ``blank=False``, the field will be required.
.. attribute:: Field.choices
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
-field.
+field. If this is given, the default form widget will be a select box with
+these choices instead of the standard text field.
-If this is given, Django's admin will use a select box instead of the standard
-text field and will limit choices to the choices given.
-
-A choices list is an iterable of 2-tuples; the first element in each
-tuple is the actual value to be stored, and the second element is the
-human-readable name. For example::
+The first element in each tuple is the actual value to be stored, and the
+second element is the human-readable name. For example::
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
@@ -176,7 +173,7 @@ scenes.
.. attribute:: Field.db_index
-If ``True``, djadmin:`django-admin.py sqlindexes <sqlindexes>` will output a
+If ``True``, :djadmin:`django-admin.py sqlindexes <sqlindexes>` will output a
``CREATE INDEX`` statement for this field.
``db_tablespace``
@@ -203,8 +200,8 @@ callable it will be called every time a new object is created.
.. attribute:: Field.editable
-If ``False``, the field will not be editable in the admin or via forms
-automatically generated from the model class. Default is ``True``.
+If ``False``, the field will not be displayed in the admin or any other
+:class:`~django.forms.ModelForm`. Default is ``True``.
``error_messages``
------------------
@@ -224,11 +221,11 @@ the `Field types`_ section below.
.. attribute:: Field.help_text
-Extra "help" text to be displayed under the field on the object's admin form.
-It's useful for documentation even if your object doesn't have an admin form.
+Extra "help" text to be displayed with the form widget. It's useful for
+documentation even if your field isn't used on a form.
-Note that this value is *not* HTML-escaped when it's displayed in the admin
-interface. This lets you include HTML in :attr:`~Field.help_text` if you so
+Note that this value is *not* HTML-escaped in automatically-generated
+forms. This lets you include HTML in :attr:`~Field.help_text` if you so
desire. For example::
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
@@ -259,7 +256,7 @@ Only one primary key is allowed on an object.
If ``True``, this field must be unique throughout the table.
-This is enforced at the database level and at the Django admin-form level. If
+This is enforced at the database level and by model validation. If
you try to save a model with a duplicate value in a :attr:`~Field.unique`
field, a :exc:`django.db.IntegrityError` will be raised by the model's
:meth:`~django.db.models.Model.save` method.
@@ -279,7 +276,7 @@ For example, if you have a field ``title`` that has
``unique_for_date="pub_date"``, then Django wouldn't allow the entry of two
records with the same ``title`` and ``pub_date``.
-This is enforced at the Django admin-form level but not at the database level.
+This is enforced by model validation but not at the database level.
``unique_for_month``
--------------------
@@ -337,7 +334,7 @@ otherwise. See :ref:`automatic-primary-key-fields`.
A 64 bit integer, much like an :class:`IntegerField` except that it is
guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The
-admin represents this as an ``<input type="text">`` (a single-line input).
+default form widget for this field is a :class:`~django.forms.TextInput`.
``BooleanField``
@@ -347,7 +344,8 @@ admin represents this as an ``<input type="text">`` (a single-line input).
A true/false field.
-The admin represents this as a checkbox.
+The default form widget for this field is a
+:class:`~django.forms.CheckboxInput`.
If you need to accept :attr:`~Field.null` values then use
:class:`NullBooleanField` instead.
@@ -361,7 +359,7 @@ A string field, for small- to large-sized strings.
For large amounts of text, use :class:`~django.db.models.TextField`.
-The admin represents this as an ``<input type="text">`` (a single-line input).
+The default form widget for this field is a :class:`~django.forms.TextInput`.
:class:`CharField` has one extra required argument:
@@ -414,9 +412,10 @@ optional arguments:
for creation of timestamps. Note that the current date is *always* used;
it's not just a default value that you can override.
-The admin represents this as an ``<input type="text">`` with a JavaScript
-calendar, and a shortcut for "Today". Includes an additional ``invalid_date``
-error message key.
+The default form widget for this field is a
+:class:`~django.forms.TextInput`. The admin adds a JavaScript calendar,
+and a shortcut for "Today". Includes an additional ``invalid_date`` error
+message key.
.. note::
As currently implemented, setting ``auto_now`` or ``auto_now_add`` to
@@ -431,8 +430,9 @@ error message key.
A date and time, represented in Python by a ``datetime.datetime`` instance.
Takes the same extra arguments as :class:`DateField`.
-The admin represents this as two ``<input type="text">`` fields, with
-JavaScript shortcuts.
+The default form widget for this field is a single
+:class:`~django.forms.TextInput`. The admin uses two separate
+:class:`~django.forms.TextInput` widgets with JavaScript shortcuts.
``DecimalField``
----------------
@@ -461,7 +461,7 @@ decimal places::
models.DecimalField(..., max_digits=19, decimal_places=10)
-The admin represents this as an ``<input type="text">`` (a single-line input).
+The default form widget for this field is a :class:`~django.forms.TextInput`.
.. note::
@@ -539,8 +539,8 @@ Also has one optional argument:
Optional. A storage object, which handles the storage and retrieval of your
files. See :doc:`/topics/files` for details on how to provide this object.
-The admin represents this field as an ``<input type="file">`` (a file-upload
-widget).
+The default form widget for this field is a
+:class:`~django.forms.widgets.FileInput`.
Using a :class:`FileField` or an :class:`ImageField` (see below) in a model
takes a few steps:
@@ -725,7 +725,7 @@ can change the maximum length using the :attr:`~CharField.max_length` argument.
A floating-point number represented in Python by a ``float`` instance.
-The admin represents this as an ``<input type="text">`` (a single-line input).
+The default form widget for this field is a :class:`~django.forms.TextInput`.
.. _floatfield_vs_decimalfield:
@@ -776,16 +776,16 @@ length using the :attr:`~CharField.max_length` argument.
.. class:: IntegerField([**options])
-An integer. The admin represents this as an ``<input type="text">`` (a
-single-line input).
+An integer. The default form widget for this field is a
+:class:`~django.forms.TextInput`.
``IPAddressField``
------------------
.. class:: IPAddressField([**options])
-An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
-as an ``<input type="text">`` (a single-line input).
+An IP address, in string format (e.g. "192.0.2.30"). The default form widget
+for this field is a :class:`~django.forms.TextInput`.
``GenericIPAddressField``
-------------------------
@@ -795,8 +795,8 @@ as an ``<input type="text">`` (a single-line input).
.. versionadded:: 1.4
An IPv4 or IPv6 address, in string format (e.g. ``192.0.2.30`` or
-``2a02:42fe::4``). The admin represents this as an ``<input type="text">``
-(a single-line input).
+``2a02:42fe::4``). The default form widget for this field is a
+:class:`~django.forms.TextInput`.
The IPv6 address normalization follows :rfc:`4291#section-2.2` section 2.2,
including using the IPv4 format suggested in paragraph 3 of that section, like
@@ -823,8 +823,8 @@ are converted to lowercase.
.. class:: NullBooleanField([**options])
Like a :class:`BooleanField`, but allows ``NULL`` as one of the options. Use
-this instead of a :class:`BooleanField` with ``null=True``. The admin represents
-this as a ``<select>`` box with "Unknown", "Yes" and "No" choices.
+this instead of a :class:`BooleanField` with ``null=True``. The default form
+widget for this field is a :class:`~django.forms.NullBooleanSelect`.
``PositiveIntegerField``
------------------------
@@ -875,8 +875,8 @@ Like an :class:`IntegerField`, but only allows values under a certain
.. class:: TextField([**options])
-A large text field. The admin represents this as a ``<textarea>`` (a multi-line
-input).
+A large text field. The default form widget for this field is a
+:class:`~django.forms.Textarea`.
.. admonition:: MySQL users
@@ -893,8 +893,8 @@ input).
A time, represented in Python by a ``datetime.time`` instance. Accepts the same
auto-population options as :class:`DateField`.
-The admin represents this as an ``<input type="text">`` with some JavaScript
-shortcuts.
+The default form widget for this field is a :class:`~django.forms.TextInput`.
+The admin adds some JavaScript shortcuts.
``URLField``
------------
@@ -903,7 +903,7 @@ shortcuts.
A :class:`CharField` for a URL.
-The admin represents this as an ``<input type="text">`` (a single-line input).
+The default form widget for this field is a :class:`~django.forms.TextInput`.
Like all :class:`CharField` subclasses, :class:`URLField` takes the optional
:attr:`~CharField.max_length`argument. If you don't specify
@@ -979,9 +979,9 @@ define the details of how the relation works.
.. attribute:: ForeignKey.limit_choices_to
A dictionary of lookup arguments and values (see :doc:`/topics/db/queries`)
- that limit the available admin choices for this object. Use this with
- functions from the Python ``datetime`` module to limit choices of objects by
- date. For example::
+ that limit the available admin or ModelForm choices for this object. Use
+ this with functions from the Python ``datetime`` module to limit choices of
+ objects by date. For example::
limit_choices_to = {'pub_date__lte': datetime.now}
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index 14541ad0d1..472ac96457 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -48,7 +48,7 @@ that, you need to :meth:`~Model.save()`.
2. Add a method on a custom manager (usually preferred)::
class BookManager(models.Manager):
- def create_book(title):
+ def create_book(self, title):
book = self.create(title=title)
# do something with the book
return book
@@ -386,6 +386,12 @@ perform an update on all fields.
Specifying ``update_fields`` will force an update.
+When saving a model fetched through deferred model loading
+(:meth:`~Model.only()` or :meth:`~Model.defer()`) only the fields loaded from
+the DB will get updated. In effect there is an automatic ``update_fields`` in
+this case. If you assign or change any deferred field value, these fields will
+be added to the updated fields.
+
Deleting objects
================
@@ -453,9 +459,9 @@ using ``__str__()`` like this::
last_name = models.CharField(max_length=50)
def __str__(self):
- # Note use of django.utils.encoding.smart_bytes() here because
+ # Note use of django.utils.encoding.force_bytes() here because
# first_name and last_name will be unicode strings.
- return smart_bytes('%s %s' % (self.first_name, self.last_name))
+ return force_bytes('%s %s' % (self.first_name, self.last_name))
``get_absolute_url``
--------------------
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 8c188c67c3..b59b2ece82 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -1110,6 +1110,14 @@ one, doing so will result in an error.
reader, is slightly faster and consumes a little less memory in the Python
process.
+.. versionchanged:: 1.5
+
+.. note::
+
+ When calling :meth:`~Model.save()` for instances with deferred fields,
+ only the loaded fields will be saved. See :meth:`~Model.save()` for more
+ details.
+
only
~~~~
@@ -1154,6 +1162,14 @@ All of the cautions in the note for the :meth:`defer` documentation apply to
options. Also note that using :meth:`only` and omitting a field requested
using :meth:`select_related` is an error as well.
+.. versionchanged:: 1.5
+
+.. note::
+
+ When calling :meth:`~Model.save()` for instances with deferred fields,
+ only the loaded fields will be saved. See :meth:`~Model.save()` for more
+ details.
+
using
~~~~~
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index 551ee00c6b..21e99de10d 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -731,10 +731,11 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
.. class:: HttpResponseRedirect
- The constructor takes a single argument -- the path to redirect to. This
- can be a fully qualified URL (e.g. ``'http://www.yahoo.com/search/'``) or
- an absolute path with no domain (e.g. ``'/search/'``). Note that this
- returns an HTTP status code 302.
+ The first argument to the constructor is required -- the path to redirect
+ to. This can be a fully qualified URL
+ (e.g. ``'http://www.yahoo.com/search/'``) or an absolute path with no
+ domain (e.g. ``'/search/'``). See :class:`HttpResponse` for other optional
+ constructor arguments. Note that this returns an HTTP status code 302.
.. class:: HttpResponsePermanentRedirect
@@ -743,8 +744,9 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
.. class:: HttpResponseNotModified
- The constructor doesn't take any arguments. Use this to designate that a
- page hasn't been modified since the user's last request (status code 304).
+ The constructor doesn't take any arguments and no content should be added
+ to this response. Use this to designate that a page hasn't been modified
+ since the user's last request (status code 304).
.. class:: HttpResponseBadRequest
@@ -760,8 +762,9 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
.. class:: HttpResponseNotAllowed
- Like :class:`HttpResponse`, but uses a 405 status code. Takes a single,
- required argument: a list of permitted methods (e.g. ``['GET', 'POST']``).
+ Like :class:`HttpResponse`, but uses a 405 status code. The first argument
+ to the constructor is required: a list of permitted methods (e.g.
+ ``['GET', 'POST']``).
.. class:: HttpResponseGone
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 531ff33da2..4729a2b6f1 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -183,7 +183,7 @@ compose a prefix, version and key into a final cache key. The default
implementation is equivalent to the function::
def make_key(key, key_prefix, version):
- return ':'.join([key_prefix, str(version), smart_bytes(key)])
+ return ':'.join([key_prefix, str(version), key])
You may use any key function you want, as long as it has the same
argument signature.
@@ -747,14 +747,15 @@ Did you catch that? NEVER deploy a site into production with :setting:`DEBUG`
turned on.
One of the main features of debug mode is the display of detailed error pages.
-If your app raises an exception when ``DEBUG`` is ``True``, Django will display
-a detailed traceback, including a lot of metadata about your environment, such
-as all the currently defined Django settings (from ``settings.py``).
+If your app raises an exception when :setting:`DEBUG` is ``True``, Django will
+display a detailed traceback, including a lot of metadata about your
+environment, such as all the currently defined Django settings (from
+``settings.py``).
As a security measure, Django will *not* include settings that might be
-sensitive (or offensive), such as ``SECRET_KEY`` or ``PROFANITIES_LIST``.
-Specifically, it will exclude any setting whose name includes any of the
-following:
+sensitive (or offensive), such as :setting:`SECRET_KEY` or
+:setting:`PROFANITIES_LIST`. Specifically, it will exclude any setting whose
+name includes any of the following:
* API
* KEY
@@ -1356,7 +1357,7 @@ MANAGERS
Default: ``()`` (Empty tuple)
A tuple in the same format as :setting:`ADMINS` that specifies who should get
-broken-link notifications when ``SEND_BROKEN_LINK_EMAILS=True``.
+broken-link notifications when :setting:`SEND_BROKEN_LINK_EMAILS` is ``True``.
.. setting:: MEDIA_ROOT
@@ -1365,8 +1366,8 @@ MEDIA_ROOT
Default: ``''`` (Empty string)
-Absolute path to the directory that holds media for this installation, used
-for :doc:`managing stored files </topics/files>`.
+Absolute filesystem path to the directory that will hold :doc:`user-uploaded
+files </topics/files>`.
Example: ``"/var/www/example.com/media/"``
@@ -1537,9 +1538,23 @@ SECRET_KEY
Default: ``''`` (Empty string)
-A secret key for this particular Django installation. Used to provide a seed in
-secret-key hashing algorithms. Set this to a random string -- the longer, the
-better. ``django-admin.py startproject`` creates one automatically.
+A secret key for a particular Django installation. This is used to provide
+:doc:`cryptographic signing </topics/signing>`, and should be set to a unique,
+unpredictable value.
+
+:djadmin:`django-admin.py startproject <startproject>` automatically adds a
+randomly-generated ``SECRET_KEY`` to each new project.
+
+.. warning::
+
+ **Keep this value secret.**
+
+ Running Django with a known :setting:`SECRET_KEY` defeats many of Django's
+ security protections, and can lead to privilege escalation and remote code
+ execution vulnerabilities.
+
+.. versionchanged:: 1.5
+ Django will now refuse to start if :setting:`SECRET_KEY` is not set.
.. setting:: SECURE_PROXY_SSL_HEADER
diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
index bd2b4c6e9d..48bd346788 100644
--- a/docs/ref/templates/api.txt
+++ b/docs/ref/templates/api.txt
@@ -125,6 +125,10 @@ dot in a variable name, it tries the following lookups, in this order:
* Attribute lookup. Example: ``foo.bar``
* List-index lookup. Example: ``foo[bar]``
+Note that "bar" in a template expression like ``{{ foo.bar }}`` will be
+interpreted as a literal string and not using the value of the variable "bar",
+if one exists in the template context.
+
The template system uses the first lookup type that works. It's short-circuit
logic. Here are a few examples::
@@ -198,6 +202,8 @@ straight lookups. Here are some things to keep in mind:
* A variable can only be called if it has no required arguments. Otherwise,
the system will return an empty string.
+.. _alters-data-description:
+
* Obviously, there can be side effects when calling some variables, and
it'd be either foolish or a security hole to allow the template system
to access them.
@@ -422,6 +428,10 @@ optional, third positional argument, ``processors``. In this example, the
my_data_dictionary,
context_instance=RequestContext(request))
+ Alternatively, use the :meth:`~django.shortcuts.render()` shortcut which is
+ the same as a call to :func:`~django.shortcuts.render_to_response()` with a
+ context_instance argument that forces the use of a ``RequestContext``.
+
Here's what each of the default processors does:
django.contrib.auth.context_processors.auth
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index b6cb1035d3..de19578cac 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -187,6 +187,14 @@ The functions defined in this module share the following properties:
Useful as a mix-in. If you support Python 2 and 3 with a single code base,
you can inherit this mix-in and just define ``__unicode__``.
+.. function:: python_2_unicode_compatible
+
+ A decorator that defines ``__unicode__`` and ``__str__`` methods under
+ Python 2. Under Python 3 it does nothing.
+
+ To support Python 2 and 3 with a single code base, define a ``__str__``
+ method returning text and apply this decorator to the class.
+
.. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict')
.. versionadded:: 1.5
@@ -232,14 +240,29 @@ The functions defined in this module share the following properties:
If ``strings_only`` is ``True``, don't convert (some) non-string-like
objects.
+.. function:: force_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
+
+ .. versionadded:: 1.5
+
+ Similar to ``smart_bytes``, except that lazy instances are resolved to
+ bytestrings, rather than kept as lazy objects.
+
+ If ``strings_only`` is ``True``, don't convert (some) non-string-like
+ objects.
+
.. function:: smart_str(s, encoding='utf-8', strings_only=False, errors='strict')
Alias of :func:`smart_bytes` on Python 2 and :func:`smart_text` on Python
- 3. This function always returns a :class:`str`.
+ 3. This function returns a :class:`str` or a lazy string.
For instance, this is suitable for writing to :attr:`sys.stdout` on
Python 2 and 3.
+.. function:: force_str(s, encoding='utf-8', strings_only=False, errors='strict')
+
+ Alias of :func:`force_bytes` on Python 2 and :func:`force_text` on Python
+ 3. This function always returns a :class:`str`.
+
.. function:: iri_to_uri(iri)
Convert an Internationalized Resource Identifier (IRI) portion to a URI
@@ -478,6 +501,33 @@ escaping HTML.
through :func:`conditional_escape` which (ultimately) calls
:func:`~django.utils.encoding.force_text` on the values.
+.. function:: strip_tags(value)
+
+ Removes anything that looks like an html tag from the string, that is
+ anything contained within ``<>``.
+
+ For example::
+
+ strip_tags(value)
+
+ If ``value`` is ``"<b>Joel</b> <button>is</button> a <span>slug</span>"`` the
+ return value will be ``"Joel is a slug"``.
+
+.. function:: remove_tags(value, tags)
+
+ Removes a list of [X]HTML tag names from the output.
+
+ For example::
+
+ remove_tags(value, ["b", "span"])
+
+ If ``value`` is ``"<b>Joel</b> <button>is</button> a <span>slug</span>"`` the
+ return value will be ``"Joel <button>is</button> a slug"``.
+
+ Note that this filter is case-sensitive.
+
+ If ``value`` is ``"<B>Joel</B> <button>is</button> a <span>slug</span>"`` the
+ return value will be ``"<B>Joel</B> <button>is</button> a slug"``.
.. _str.format: http://docs.python.org/library/stdtypes.html#str.format
@@ -552,15 +602,29 @@ string" means that the producer of the string has already turned characters
that should not be interpreted by the HTML engine (e.g. '<') into the
appropriate entities.
+.. class:: SafeBytes
+
+ .. versionadded:: 1.5
+
+ A :class:`bytes` subclass that has been specifically marked as "safe"
+ (requires no further escaping) for HTML output purposes.
+
.. class:: SafeString
- A string subclass that has been specifically marked as "safe" (requires no
- further escaping) for HTML output purposes.
+ A :class:`str` subclass that has been specifically marked as "safe"
+ (requires no further escaping) for HTML output purposes. This is
+ :class:`SafeBytes` on Python 2 and :class:`SafeText` on Python 3.
+
+.. class:: SafeText
+
+ .. versionadded:: 1.5
+
+ A :class:`str` (in Python 3) or :class:`unicode` (in Python 2) subclass
+ that has been specifically marked as "safe" for HTML output purposes.
.. class:: SafeUnicode
- A unicode subclass that has been specifically marked as "safe" for HTML
- output purposes.
+ Historical name of :class:`SafeText`. Only available under Python 2.
.. function:: mark_safe(s)
@@ -577,6 +641,24 @@ appropriate entities.
Can be called multiple times on a single string (the resulting escaping is
only applied once).
+``django.utils.text``
+=====================
+
+.. module:: django.utils.text
+ :synopsis: Text manipulation.
+
+.. function:: slugify
+
+ Converts to lowercase, removes non-word characters (alphanumerics and
+ underscores) and converts spaces to hyphens. Also strips leading and trailing
+ whitespace.
+
+ For example::
+
+ slugify(value)
+
+ If ``value`` is ``"Joel is a slug"``, the output will be ``"joel-is-a-slug"``.
+
``django.utils.translation``
============================
diff --git a/docs/releases/1.3.2.txt b/docs/releases/1.3.2.txt
new file mode 100644
index 0000000000..16e00f9732
--- /dev/null
+++ b/docs/releases/1.3.2.txt
@@ -0,0 +1,14 @@
+==========================
+Django 1.3.2 release notes
+==========================
+
+*July 30, 2012*
+
+This is the second security release in the Django 1.3 series, fixing several
+security issues in Django 1.3. Django 1.3.2 is a recommended upgrade for
+all users of Django 1.3.
+
+For a full list of issues addressed in this release, see the `security
+advisory`_.
+
+.. _security advisory: https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/
diff --git a/docs/releases/1.4.1.txt b/docs/releases/1.4.1.txt
new file mode 100644
index 0000000000..7c6568e5b5
--- /dev/null
+++ b/docs/releases/1.4.1.txt
@@ -0,0 +1,14 @@
+==========================
+Django 1.4.1 release notes
+==========================
+
+*July 30, 2012*
+
+This is the first security release in the Django 1.4 series, fixing several
+security issues in Django 1.4. Django 1.4.1 is a recommended upgrade for
+all users of Django 1.4.
+
+For a full list of issues addressed in this release, see the `security
+advisory`_.
+
+.. _security advisory: https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/
diff --git a/docs/releases/1.4.2.txt b/docs/releases/1.4.2.txt
new file mode 100644
index 0000000000..6f2e9aca2e
--- /dev/null
+++ b/docs/releases/1.4.2.txt
@@ -0,0 +1,14 @@
+==========================
+Django 1.4.2 release notes
+==========================
+
+*TO BE RELEASED*
+
+This is the second security release in the Django 1.4 series.
+
+Backwards incompatible changes
+==============================
+
+* The newly introduced :class:`~django.db.models.GenericIPAddressField`
+ constructor arguments have been adapted to match those of all other model
+ fields. The first two keyword arguments are now verbose_name and name.
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index f789ebde40..5728d8559a 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -42,6 +42,10 @@ keyword argument ``update_fields``. By using this argument it is possible to
save only a select list of model's fields. This can be useful for performance
reasons or when trying to avoid overwriting concurrent changes.
+Deferred instances (those loaded by .only() or .defer()) will automatically
+save just the loaded fields. If any field is set manually after load, that
+field will also get updated on save.
+
See the :meth:`Model.save() <django.db.models.Model.save()>` documentation for
more details.
@@ -80,6 +84,12 @@ By passing ``False`` using this argument it is now possible to retreive the
:class:`ContentType <django.contrib.contenttypes.models.ContentType>`
associated with proxy models.
+New ``view`` variable in class-based views context
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In all :doc:`generic class-based views </topics/class-based-views/index>`
+(or any class-based view inheriting from ``ContextMixin``), the context dictionary
+contains a ``view`` variable that points to the ``View`` instance.
+
Minor features
~~~~~~~~~~~~~~
@@ -134,6 +144,14 @@ year|date:"Y" }}``.
``next_year`` and ``previous_year`` were also added in the context. They are
calculated according to ``allow_empty`` and ``allow_future``.
+Context in TemplateView
+~~~~~~~~~~~~~~~~~~~~~~~
+
+For consistency with the design of the other generic views,
+:class:`~django.views.generic.base.TemplateView` no longer passes a ``params``
+dictionary into the context, instead passing the variables from the URLconf
+directly into the context.
+
OPTIONS, PUT and DELETE requests in the test client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -154,6 +172,54 @@ If you were using the ``data`` parameter in a PUT request without a
``content_type``, you must encode your data before passing it to the test
client and set the ``content_type`` argument.
+.. _simplejson-incompatibilities:
+
+System version of :mod:`simplejson` no longer used
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:ref:`As explained below <simplejson-deprecation>`, Django 1.5 deprecates
+:mod:`django.utils.simplejson` in favor of Python 2.6's built-in :mod:`json`
+module. In theory, this change is harmless. Unfortunately, because of
+incompatibilities between versions of :mod:`simplejson`, it may trigger errors
+in some circumstances.
+
+JSON-related features in Django 1.4 always used :mod:`django.utils.simplejson`.
+This module was actually:
+
+- A system version of :mod:`simplejson`, if one was available (ie. ``import
+ simplejson`` works), if it was more recent than Django's built-in copy or it
+ had the C speedups, or
+- The :mod:`json` module from the standard library, if it was available (ie.
+ Python 2.6 or greater), or
+- A built-in copy of version 2.0.7 of :mod:`simplejson`.
+
+In Django 1.5, those features use Python's :mod:`json` module, which is based
+on version 2.0.9 of :mod:`simplejson`.
+
+There are no known incompatibilities between Django's copy of version 2.0.7 and
+Python's copy of version 2.0.9. However, there are some incompatibilities
+between other versions of :mod:`simplejson`:
+
+- While the :mod:`simplejson` API is documented as always returning unicode
+ strings, the optional C implementation can return a byte string. This was
+ fixed in Python 2.7.
+- :class:`simplejson.JSONEncoder` gained a ``namedtuple_as_object`` keyword
+ argument in version 2.2.
+
+More information on these incompatibilities is available in `ticket #18023`_.
+
+The net result is that, if you have installed :mod:`simplejson` and your code
+uses Django's serialization internals directly -- for instance
+:class:`django.core.serializers.json.DjangoJSONEncoder`, the switch from
+:mod:`simplejson` to :mod:`json` could break your code. (In general, changes to
+internals aren't documented; we're making an exception here.)
+
+At this point, the maintainers of Django believe that using :mod:`json` from
+the standard library offers the strongest guarantee of backwards-compatibility.
+They recommend to use it from now on.
+
+.. _ticket #18023: https://code.djangoproject.com/ticket/18023#comment:10
+
String types of hasher method parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -161,7 +227,7 @@ If you have written a :ref:`custom password hasher <auth_password_storage>`,
your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept
Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the
hashing methods need byte strings, you can use the
-:func:`~django.utils.encoding.smart_bytes` utility to encode the strings.
+:func:`~django.utils.encoding.force_bytes` utility to encode the strings.
Validation of previous_page_number and next_page_number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -257,19 +323,38 @@ Miscellaneous
* :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError`
instead of :exc:`ValueError` for non-integer inputs.
+* The ``slugify`` template filter is now available as a standard python
+ function at :func:`django.utils.text.slugify`. Similarly, ``remove_tags`` is
+ available at :func:`django.utils.html.remove_tags`.
+
Features deprecated in 1.5
==========================
+.. _simplejson-deprecation:
+
``django.utils.simplejson``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since Django 1.5 drops support for Python 2.5, we can now rely on the
-:mod:`json` module being in Python's standard library -- so we've removed
-our own copy of ``simplejson``. You can safely change any use of
-:mod:`django.utils.simplejson` to :mod:`json`.
+:mod:`json` module being available in Python's standard library, so we've
+removed our own copy of :mod:`simplejson`. You should now import :mod:`json`
+instead :mod:`django.utils.simplejson`.
+
+Unfortunately, this change might have unwanted side-effects, because of
+incompatibilities between versions of :mod:`simplejson` -- see the
+:ref:`backwards-incompatible changes <simplejson-incompatibilities>` section.
+If you rely on features added to :mod:`simplejson` after it became Python's
+:mod:`json`, you should import :mod:`simplejson` explicitly.
``itercompat.product``
~~~~~~~~~~~~~~~~~~~~~~
The :func:`~django.utils.itercompat.product` function has been deprecated. Use
the built-in :func:`itertools.product` instead.
+
+``django.utils.encoding.StrAndUnicode``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated.
+Define a ``__str__`` method and apply the
+:func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index e38ab646d6..fa55a4d206 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -27,6 +27,8 @@ Final releases
.. toctree::
:maxdepth: 1
+ .. 1.4.2 (uncomment on release)
+ 1.4.1
1.4
1.3 release
@@ -34,6 +36,7 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.3.2
1.3.1
1.3
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index 307691bd4a..f8dbbb65a6 100644
--- a/docs/topics/auth.txt
+++ b/docs/topics/auth.txt
@@ -460,7 +460,7 @@ algorithm.
Increasing the work factor
~~~~~~~~~~~~~~~~~~~~~~~~~~
-The PDKDF2 and bcrypt algorithms use a number of iterations or rounds of
+The PBKDF2 and bcrypt algorithms use a number of iterations or rounds of
hashing. This deliberately slows down attackers, making attacks against hashed
passwords harder. However, as computing power increases, the number of
iterations needs to be increased. We've chosen a reasonable default (and will
@@ -468,7 +468,7 @@ increase it with each release of Django), but you may wish to tune it up or
down, depending on your security needs and available processing power. To do so,
you'll subclass the appropriate algorithm and override the ``iterations``
parameters. For example, to increase the number of iterations used by the
-default PDKDF2 algorithm:
+default PBKDF2 algorithm:
1. Create a subclass of ``django.contrib.auth.hashers.PBKDF2PasswordHasher``::
@@ -1170,24 +1170,25 @@ includes a few other useful built-in views located in
:file:`registration/password_reset_form.html` if not supplied.
* ``email_template_name``: The full name of a template to use for
- generating the email with the new password. Defaults to
+ generating the email with the reset password link. Defaults to
:file:`registration/password_reset_email.html` if not supplied.
* ``subject_template_name``: The full name of a template to use for
- the subject of the email with the new password. Defaults
+ the subject of the email with the reset password link. Defaults
to :file:`registration/password_reset_subject.txt` if not supplied.
.. versionadded:: 1.4
- * ``password_reset_form``: Form that will be used to set the password.
- Defaults to :class:`~django.contrib.auth.forms.PasswordResetForm`.
+ * ``password_reset_form``: Form that will be used to get the email of
+ the user to reset the password for. Defaults to
+ :class:`~django.contrib.auth.forms.PasswordResetForm`.
- * ``token_generator``: Instance of the class to check the password. This
- will default to ``default_token_generator``, it's an instance of
+ * ``token_generator``: Instance of the class to check the one time link.
+ This will default to ``default_token_generator``, it's an instance of
``django.contrib.auth.tokens.PasswordResetTokenGenerator``.
* ``post_reset_redirect``: The URL to redirect to after a successful
- password change.
+ password reset request.
* ``from_email``: A valid email address. By default Django uses
the :setting:`DEFAULT_FROM_EMAIL`.
@@ -1218,7 +1219,7 @@ includes a few other useful built-in views located in
* ``uid``: The user's id encoded in base 36.
- * ``token``: Token to check that the password is valid.
+ * ``token``: Token to check that the reset link is valid.
Sample ``registration/password_reset_email.html`` (email body template):
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 219b6c7795..f13238e342 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -864,7 +864,7 @@ key version to provide a final cache key. By default, the three parts
are joined using colons to produce a final string::
def make_key(key, key_prefix, version):
- return ':'.join([key_prefix, str(version), smart_bytes(key)])
+ return ':'.join([key_prefix, str(version), key])
If you want to combine the parts in different ways, or apply other
processing to the final key (e.g., taking a hash digest of the key
diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt
index 1ac70e6938..6637bd5fcb 100644
--- a/docs/topics/class-based-views/index.txt
+++ b/docs/topics/class-based-views/index.txt
@@ -11,8 +11,7 @@ to structure your views and reuse code by harnessing inheritance and
mixins. There are also some generic views for simple tasks which we'll
get to later, but you may want to design your own structure of
reusable views which suits your use case. For full details, see the
-:doc:`class-based views reference
-documentation</ref/class-based-views/index>`.
+:doc:`class-based views reference documentation</ref/class-based-views/index>`.
.. toctree::
:maxdepth: 1
@@ -32,41 +31,12 @@ redirect, and :class:`~django.views.generic.base.TemplateView` extends the base
to make it also render a template.
-Simple usage
-============
-
-Class-based generic views (and any class-based views that inherit from
-the base classes Django provides) can be configured in two
-ways: subclassing, or passing in arguments directly in the URLconf.
-
-When you subclass a class-based view, you can override attributes
-(such as the ``template_name``) or methods (such as ``get_context_data``)
-in your subclass to provide new values or methods. Consider, for example,
-a view that just displays one template, ``about.html``. Django has a
-generic view to do this - :class:`~django.views.generic.base.TemplateView` -
-so we can just subclass it, and override the template name::
-
- # some_app/views.py
- from django.views.generic import TemplateView
-
- class AboutView(TemplateView):
- template_name = "about.html"
-
-Then, we just need to add this new view into our URLconf. As the class-based
-views themselves are classes, we point the URL to the ``as_view`` class method
-instead, which is the entry point for class-based views::
-
- # urls.py
- from django.conf.urls import patterns, url, include
- from some_app.views import AboutView
-
- urlpatterns = patterns('',
- (r'^about/', AboutView.as_view()),
- )
+Simple usage in your URLconf
+============================
-Alternatively, if you're only changing a few simple attributes on a
-class-based view, you can simply pass the new attributes into the ``as_view``
-method call itself::
+The simplest way to use generic views is to create them directly in your
+URLconf. If you're only changing a few simple attributes on a class-based view,
+you can simply pass them into the ``as_view`` method call itself::
from django.conf.urls import patterns, url, include
from django.views.generic import TemplateView
@@ -75,93 +45,41 @@ method call itself::
(r'^about/', TemplateView.as_view(template_name="about.html")),
)
+Any arguments given will override the ``template_name`` on the
A similar overriding pattern can be used for the ``url`` attribute on
:class:`~django.views.generic.base.RedirectView`.
-.. _jsonresponsemixin-example:
-
-More than just HTML
--------------------
-Where class based views shine is when you want to do the same thing many times.
-Suppose you're writing an API, and every view should return JSON instead of
-rendered HTML.
+Subclassing generic views
+=========================
-We can create a mixin class to use in all of our views, handling the
-conversion to JSON once.
+The second, more powerful way to use generic views is to inherit from an
+existing view and override attributes (such as the ``template_name``) or
+methods (such as ``get_context_data``) in your subclass to provide new values
+or methods. Consider, for example, a view that just displays one template,
+``about.html``. Django has a generic view to do this -
+:class:`~django.views.generic.base.TemplateView` - so we can just subclass it,
+and override the template name::
-For example, a simple JSON mixin might look something like this::
-
- import json
- from django.http import HttpResponse
-
- class JSONResponseMixin(object):
- """
- A mixin that can be used to render a JSON response.
- """
- response_class = HttpResponse
-
- def render_to_response(self, context, **response_kwargs):
- """
- Returns a JSON response, transforming 'context' to make the payload.
- """
- response_kwargs['content_type'] = 'application/json'
- return self.response_class(
- self.convert_context_to_json(context),
- **response_kwargs
- )
-
- def convert_context_to_json(self, context):
- "Convert the context dictionary into a JSON object"
- # Note: This is *EXTREMELY* naive; in reality, you'll need
- # to do much more complex handling to ensure that arbitrary
- # objects -- such as Django model instances or querysets
- # -- can be serialized as JSON.
- return json.dumps(context)
-
-Now we mix this into the base view::
-
- from django.views.generic import View
-
- class JSONView(JSONResponseMixin, View):
- pass
-
-Equally we could use our mixin with one of the generic views. We can make our
-own version of :class:`~django.views.generic.detail.DetailView` by mixing
-:class:`JSONResponseMixin` with the
-:class:`~django.views.generic.detail.BaseDetailView` -- (the
-:class:`~django.views.generic.detail.DetailView` before template
-rendering behavior has been mixed in)::
+ # some_app/views.py
+ from django.views.generic import TemplateView
- class JSONDetailView(JSONResponseMixin, BaseDetailView):
- pass
+ class AboutView(TemplateView):
+ template_name = "about.html"
-This view can then be deployed in the same way as any other
-:class:`~django.views.generic.detail.DetailView`, with exactly the
-same behavior -- except for the format of the response.
+Then we just need to add this new view into our URLconf.
+`~django.views.generic.base.TemplateView` is a class, not a function, so we
+point the URL to the ``as_view`` class method instead, which provides a
+function-like entry to class-based views::
-If you want to be really adventurous, you could even mix a
-:class:`~django.views.generic.detail.DetailView` subclass that is able
-to return *both* HTML and JSON content, depending on some property of
-the HTTP request, such as a query argument or a HTTP header. Just mix
-in both the :class:`JSONResponseMixin` and a
-:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
-and override the implementation of :func:`render_to_response()` to defer
-to the appropriate subclass depending on the type of response that the user
-requested::
+ # urls.py
+ from django.conf.urls import patterns, url, include
+ from some_app.views import AboutView
- class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
- def render_to_response(self, context):
- # Look for a 'format=json' GET argument
- if self.request.GET.get('format','html') == 'json':
- return JSONResponseMixin.render_to_response(self, context)
- else:
- return SingleObjectTemplateResponseMixin.render_to_response(self, context)
+ urlpatterns = patterns('',
+ (r'^about/', AboutView.as_view()),
+ )
-Because of the way that Python resolves method overloading, the local
-``render_to_response()`` implementation will override the versions provided by
-:class:`JSONResponseMixin` and
-:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
For more information on how to use the built in generic views, consult the next
topic on :doc:`generic class based views</topics/class-based-views/generic-display>`.
@@ -171,16 +89,15 @@ Decorating class-based views
.. highlightlang:: python
-The extension of class-based views isn't limited to using mixins. You
-can use also use decorators.
+Since class-based views aren't functions, decorating them works differently
+depending on if you're using ``as_view`` or creating a subclass.
Decorating in URLconf
---------------------
The simplest way of decorating class-based views is to decorate the
result of the :meth:`~django.views.generic.base.View.as_view` method.
-The easiest place to do this is in the URLconf where you deploy your
-view::
+The easiest place to do this is in the URLconf where you deploy your view::
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index 0c19d60e35..f07769fb8a 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -69,7 +69,7 @@ interface to working with templates in class-based views.
add more members to the dictionary.
Building up Django's generic class-based views
-===============================================
+==============================================
Let's look at how two of Django's generic class-based views are built
out of mixins providing discrete functionality. We'll consider
@@ -222,8 +222,7 @@ we'll want the functionality provided by
:class:`~django.views.generic.detail.SingleObjectMixin`.
We'll demonstrate this with the publisher modelling we used in the
-:doc:`generic class-based views
-introduction<generic-display>`.
+:doc:`generic class-based views introduction<generic-display>`.
.. code-block:: python
@@ -233,11 +232,11 @@ introduction<generic-display>`.
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
-
+
class RecordInterest(View, SingleObjectMixin):
"""Records the current user's interest in an author."""
model = Author
-
+
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseForbidden()
@@ -256,9 +255,7 @@ we're interested in, which it just does with a simple call to
``self.get_object()``. Everything else is taken care of for us by the
mixin.
-We can hook this into our URLs easily enough:
-
-.. code-block:: python
+We can hook this into our URLs easily enough::
# urls.py
from books.views import RecordInterest
@@ -294,8 +291,6 @@ object. In order to do this, we need to have two different querysets:
We'll figure that out ourselves in :meth:`get_queryset()` so we
can take into account the Publisher we're looking at.
-.. highlightlang:: python
-
.. note::
We have to think carefully about :meth:`get_context_data()`.
@@ -311,15 +306,15 @@ Now we can write a new :class:`PublisherDetail`::
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
-
+
class PublisherDetail(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
-
+
def get_context_data(self, **kwargs):
kwargs['publisher'] = self.object
return super(PublisherDetail, self).get_context_data(**kwargs)
-
+
def get_queryset(self):
self.object = self.get_object(Publisher.objects.all())
return self.object.book_set.all()
@@ -339,26 +334,26 @@ have to create lots of books to see the pagination working! Here's the
template you'd want to use::
{% extends "base.html" %}
-
+
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
-
+
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
-
+
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
-
+
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
-
+
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
@@ -428,9 +423,9 @@ code so that on ``POST`` the form gets called appropriately.
the views implement :meth:`get()`, and things would get much more
confusing.
-Our new :class:`AuthorDetail` looks like this:
+.. highlightlang:: python
-.. code-block:: python
+Our new :class:`AuthorDetail` looks like this::
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
@@ -455,7 +450,7 @@ Our new :class:`AuthorDetail` looks like this:
'author-detail',
kwargs = {'pk': self.object.pk},
)
-
+
def get_context_data(self, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
@@ -464,7 +459,7 @@ Our new :class:`AuthorDetail` looks like this:
}
context.update(kwargs)
return super(AuthorDetail, self).get_context_data(**context)
-
+
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
@@ -472,14 +467,14 @@ Our new :class:`AuthorDetail` looks like this:
return self.form_valid(form)
else:
return self.form_invalid(form)
-
+
def form_valid(self, form):
if not self.request.user.is_authenticated():
return HttpResponseForbidden()
self.object = self.get_object()
# record the interest using the message in form.cleaned_data
return super(AuthorDetail, self).form_valid(form)
-
+
:meth:`get_success_url()` is just providing somewhere to redirect to,
which gets used in the default implementation of
:meth:`form_valid()`. We have to provide our own :meth:`post()` as
@@ -525,21 +520,21 @@ write our own :meth:`get_context_data()` to make the
from django.views.generic import DetailView
from django import forms
from books.models import Author
-
+
class AuthorInterestForm(forms.Form):
message = forms.CharField()
-
+
class AuthorDisplay(DetailView):
-
+
queryset = Author.objects.all()
-
+
def get_context_data(self, **kwargs):
context = {
'form': AuthorInterestForm(),
}
context.update(kwargs)
return super(AuthorDisplay, self).get_context_data(**context)
-
+
Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
have to bring in :class:`SingleObjectMixin` so we can find the author
we're talking about, and we have to remember to set
@@ -550,7 +545,7 @@ template as :class:`AuthorDisplay` is using on ``GET``.
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
-
+
class AuthorInterest(FormView, SingleObjectMixin):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
@@ -561,13 +556,13 @@ template as :class:`AuthorDisplay` is using on ``GET``.
'object': self.get_object(),
}
return super(AuthorInterest, self).get_context_data(**context)
-
+
def get_success_url(self):
return reverse(
'author-detail',
kwargs = {'pk': self.object.pk},
)
-
+
def form_valid(self, form):
if not self.request.user.is_authenticated():
return HttpResponseForbidden()
@@ -588,13 +583,13 @@ using a different template.
.. code-block:: python
from django.views.generic import View
-
+
class AuthorDetail(View):
-
+
def get(self, request, *args, **kwargs):
view = AuthorDisplay.as_view()
return view(request, *args, **kwargs)
-
+
def post(self, request, *args, **kwargs):
view = AuthorInterest.as_view()
return view(request, *args, **kwargs)
@@ -603,3 +598,88 @@ This approach can also be used with any other generic class-based
views or your own class-based views inheriting directly from
:class:`View` or :class:`TemplateView`, as it keeps the different
views as separate as possible.
+
+.. _jsonresponsemixin-example:
+
+More than just HTML
+===================
+
+Where class based views shine is when you want to do the same thing many times.
+Suppose you're writing an API, and every view should return JSON instead of
+rendered HTML.
+
+We can create a mixin class to use in all of our views, handling the
+conversion to JSON once.
+
+For example, a simple JSON mixin might look something like this::
+
+ import json
+ from django.http import HttpResponse
+
+ class JSONResponseMixin(object):
+ """
+ A mixin that can be used to render a JSON response.
+ """
+ response_class = HttpResponse
+
+ def render_to_response(self, context, **response_kwargs):
+ """
+ Returns a JSON response, transforming 'context' to make the payload.
+ """
+ response_kwargs['content_type'] = 'application/json'
+ return self.response_class(
+ self.convert_context_to_json(context),
+ **response_kwargs
+ )
+
+ def convert_context_to_json(self, context):
+ "Convert the context dictionary into a JSON object"
+ # Note: This is *EXTREMELY* naive; in reality, you'll need
+ # to do much more complex handling to ensure that arbitrary
+ # objects -- such as Django model instances or querysets
+ # -- can be serialized as JSON.
+ return json.dumps(context)
+
+Now we mix this into the base TemplateView::
+
+ from django.views.generic import TemplateView
+
+ class JSONView(JSONResponseMixin, TemplateView):
+ pass
+
+Equally we could use our mixin with one of the generic views. We can make our
+own version of :class:`~django.views.generic.detail.DetailView` by mixing
+:class:`JSONResponseMixin` with the
+:class:`~django.views.generic.detail.BaseDetailView` -- (the
+:class:`~django.views.generic.detail.DetailView` before template
+rendering behavior has been mixed in)::
+
+ class JSONDetailView(JSONResponseMixin, BaseDetailView):
+ pass
+
+This view can then be deployed in the same way as any other
+:class:`~django.views.generic.detail.DetailView`, with exactly the
+same behavior -- except for the format of the response.
+
+If you want to be really adventurous, you could even mix a
+:class:`~django.views.generic.detail.DetailView` subclass that is able
+to return *both* HTML and JSON content, depending on some property of
+the HTTP request, such as a query argument or a HTTP header. Just mix
+in both the :class:`JSONResponseMixin` and a
+:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
+and override the implementation of :func:`render_to_response()` to defer
+to the appropriate subclass depending on the type of response that the user
+requested::
+
+ class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
+ def render_to_response(self, context):
+ # Look for a 'format=json' GET argument
+ if self.request.GET.get('format','html') == 'json':
+ return JSONResponseMixin.render_to_response(self, context)
+ else:
+ return SingleObjectTemplateResponseMixin.render_to_response(self, context)
+
+Because of the way that Python resolves method overloading, the local
+``render_to_response()`` implementation will override the versions provided by
+:class:`JSONResponseMixin` and
+:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
index ce15dc9535..f29cc28332 100644
--- a/docs/topics/db/models.txt
+++ b/docs/topics/db/models.txt
@@ -108,8 +108,8 @@ determine a few things:
* The database column type (e.g. ``INTEGER``, ``VARCHAR``).
-* The :doc:`widget </ref/forms/widgets>` to use in Django's admin interface,
- if you care to use it (e.g. ``<input type="text">``, ``<select>``).
+* The default :doc:`widget </ref/forms/widgets>` to use when rendering a form
+ field (e.g. ``<input type="text">``, ``<select>``).
* The minimal validation requirements, used in Django's admin and in
automatically-generated forms.
@@ -143,13 +143,13 @@ ones:
Note that this is different than :attr:`~Field.null`.
:attr:`~Field.null` is purely database-related, whereas
:attr:`~Field.blank` is validation-related. If a field has
- :attr:`blank=True <Field.blank>`, validation on Django's admin site will
+ :attr:`blank=True <Field.blank>`, form validation will
allow entry of an empty value. If a field has :attr:`blank=False
<Field.blank>`, the field will be required.
:attr:`~Field.choices`
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for
- this field. If this is given, Django's admin will use a select box
+ this field. If this is given, the default form widget will be a select box
instead of the standard text field and will limit choices to the choices
given.
@@ -164,7 +164,7 @@ ones:
)
The first element in each tuple is the value that will be stored in the
- database, the second element will be displayed by the admin interface,
+ database, the second element will be displayed by the default form widget
or in a ModelChoiceField. Given an instance of a model object, the
display value for a choices field can be accessed using the
``get_FOO_display`` method. For example::
@@ -195,9 +195,8 @@ ones:
created.
:attr:`~Field.help_text`
- Extra "help" text to be displayed under the field on the object's admin
- form. It's useful for documentation even if your object doesn't have an
- admin form.
+ Extra "help" text to be displayed with the form widget. It's useful for
+ documentation even if your field isn't used on a form.
:attr:`~Field.primary_key`
If ``True``, this field is the primary key for the model.
@@ -360,13 +359,12 @@ It doesn't matter which model has the
:class:`~django.db.models.ManyToManyField`, but you should only put it in one
of the models -- not both.
-Generally, :class:`~django.db.models.ManyToManyField` instances should go in the
-object that's going to be edited in the admin interface, if you're using
-Django's admin. In the above example, ``toppings`` is in ``Pizza`` (rather than
-``Topping`` having a ``pizzas`` :class:`~django.db.models.ManyToManyField` )
-because it's more natural to think about a pizza having toppings than a
-topping being on multiple pizzas. The way it's set up above, the ``Pizza`` admin
-form would let users select the toppings.
+Generally, :class:`~django.db.models.ManyToManyField` instances should go in
+the object that's going to be edited on a form. In the above example,
+``toppings`` is in ``Pizza`` (rather than ``Topping`` having a ``pizzas``
+:class:`~django.db.models.ManyToManyField` ) because it's more natural to think
+about a pizza having toppings than a topping being on multiple pizzas. The way
+it's set up above, the ``Pizza`` form would let users select the toppings.
.. seealso::
diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt
index 86e785a19d..03a7d3b7cd 100644
--- a/docs/topics/db/multi-db.txt
+++ b/docs/topics/db/multi-db.txt
@@ -525,7 +525,7 @@ registered with any ``Admin`` instance::
admin.site.register(Author, MultiDBModelAdmin)
admin.site.register(Publisher, PublisherAdmin)
- othersite = admin.Site('othersite')
+ othersite = admin.AdminSite('othersite')
othersite.register(Publisher, MultiDBModelAdmin)
This example sets up two admin sites. On the first site, the
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index b4d4eb1062..60437c1120 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -900,10 +900,10 @@ possible to easily create new instance with all fields' values copied. In the
simplest case, you can just set ``pk`` to ``None``. Using our blog example::
blog = Blog(name='My blog', tagline='Blogging is easy')
- blog.save() # post.pk == 1
+ blog.save() # blog.pk == 1
blog.pk = None
- blog.save() # post.pk == 2
+ blog.save() # blog.pk == 2
Things get more complicated if you use inheritance. Consider a subclass of
``Blog``::
diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt
index 4399e7572c..615dd7193c 100644
--- a/docs/topics/forms/media.txt
+++ b/docs/topics/forms/media.txt
@@ -65,9 +65,9 @@ through this property::
>>> w = CalendarWidget()
>>> print(w.media)
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/animations.js"></script>
- <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://static.example.com/actions.js"></script>
Here's a list of all possible ``Media`` options. There are no required options.
@@ -110,9 +110,9 @@ requirements::
If this last CSS definition were to be rendered, it would become the following HTML::
- <link href="http://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
- <link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
- <link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
+ <link href="http://static.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
+ <link href="http://static.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
+ <link href="http://static.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
``js``
~~~~~~
@@ -140,11 +140,11 @@ basic Calendar widget from the example above::
>>> w = FancyCalendarWidget()
>>> print(w.media)
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
- <link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/animations.js"></script>
- <script type="text/javascript" src="http://media.example.com/actions.js"></script>
- <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://static.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://static.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
The FancyCalendar widget inherits all the media from it's parent widget. If
you don't want media to be inherited in this way, add an ``extend=False``
@@ -160,8 +160,8 @@ declaration to the media declaration::
>>> w = FancyCalendarWidget()
>>> print(w.media)
- <link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+ <link href="http://static.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
If you require even more control over media inheritance, define your media
using a `dynamic property`_. Dynamic properties give you complete control over
@@ -253,12 +253,12 @@ to filter out a medium of interest. For example::
>>> w = CalendarWidget()
>>> print(w.media)
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/animations.js"></script>
- <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://static.example.com/actions.js"></script>
>>> print(w.media)['css']
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
When you use the subscript operator, the value that is returned is a new
Media object -- but one that only contains the media of interest.
@@ -283,10 +283,10 @@ the resulting Media object contains the union of the media from both files::
>>> w1 = CalendarWidget()
>>> w2 = OtherWidget()
>>> print(w1.media + w2.media)
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/animations.js"></script>
- <script type="text/javascript" src="http://media.example.com/actions.js"></script>
- <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://static.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
Media on Forms
--------------
@@ -306,10 +306,10 @@ of adding the media definitions for all widgets that are part of the form::
>>> f = ContactForm()
>>> f.media
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/animations.js"></script>
- <script type="text/javascript" src="http://media.example.com/actions.js"></script>
- <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://static.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
If you want to associate additional media with a form -- for example, CSS for form
layout -- simply add a media declaration to the form::
@@ -325,8 +325,8 @@ layout -- simply add a media declaration to the form::
>>> f = ContactForm()
>>> f.media
- <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
- <link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
- <script type="text/javascript" src="http://media.example.com/animations.js"></script>
- <script type="text/javascript" src="http://media.example.com/actions.js"></script>
- <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+ <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://static.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://static.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://static.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 0ca37745c7..8159c8850c 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -200,7 +200,9 @@ The first time you call ``is_valid()`` or access the ``errors`` attribute of a
well as :ref:`model validation <validating-objects>`. This has the side-effect
of cleaning the model you pass to the ``ModelForm`` constructor. For instance,
calling ``is_valid()`` on your form will convert any date fields on your model
-to actual date objects.
+to actual date objects. If form validation fails, only some of the updates
+may be applied. For this reason, you'll probably want to avoid reusing the
+model instance.
The ``save()`` method
diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
index 877d3b4100..b3a830c25e 100644
--- a/docs/topics/http/file-uploads.txt
+++ b/docs/topics/http/file-uploads.txt
@@ -178,6 +178,52 @@ Three settings control Django's file upload behavior:
Which means "try to upload to memory first, then fall back to temporary
files."
+Handling uploaded files with a model
+------------------------------------
+
+If you're saving a file on a :class:`~django.db.models.Model` with a
+:class:`~django.db.models.FileField`, using a :class:`~django.forms.ModelForm`
+makes this process much easier. The file object will be saved to the location
+specified by the :attr:`~django.db.models.FileField.upload_to` argument of the
+corresponding :class:`~django.db.models.FileField` when calling
+``form.save()``::
+
+ from django.http import HttpResponseRedirect
+ from django.shortcuts import render
+ from .forms import ModelFormWithFileField
+
+ def upload_file(request):
+ if request.method == 'POST':
+ form = ModelFormWithFileField(request.POST, request.FILES)
+ if form.is_valid():
+ # file is saved
+ form.save()
+ return HttpResponseRedirect('/success/url/')
+ else:
+ form = ModelFormWithFileField()
+ return render('upload.html', {'form': form})
+
+If you are constructing an object manually, you can simply assign the file
+object from :attr:`request.FILES <django.http.HttpRequest.FILES>` to the file
+field in the model::
+
+ from django.http import HttpResponseRedirect
+ from django.shortcuts import render
+ from .forms import UploadFileForm
+ from .models import ModelWithFileField
+
+ def upload_file(request):
+ if request.method == 'POST':
+ form = UploadFileForm(request.POST, request.FILES)
+ if form.is_valid():
+ instance = ModelWithFileField(file_field=request.FILES['file'])
+ instance.save()
+ return HttpResponseRedirect('/success/url/')
+ else:
+ form = UploadFileForm()
+ return render('upload.html', {'form': form})
+
+
``UploadedFile`` objects
========================
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt
index 4e75dfe55f..7297184ed3 100644
--- a/docs/topics/http/urls.txt
+++ b/docs/topics/http/urls.txt
@@ -314,6 +314,9 @@ that should be called if none of the URL patterns match.
By default, this is ``'django.views.defaults.page_not_found'``. That default
value should suffice.
+See the documentation about :ref:`the 404 (HTTP Not Found) view
+<http_not_found_view>` for more information.
+
handler500
----------
@@ -326,6 +329,9 @@ have runtime errors in view code.
By default, this is ``'django.views.defaults.server_error'``. That default
value should suffice.
+See the documentation about :ref:`the 500 (HTTP Internal Server Error) view
+<http_internal_server_error_view>` for more information.
+
Notes on capturing text in URLs
===============================
@@ -568,10 +574,8 @@ For example::
(r'^blog/(?P<year>\d{4})/$', 'year_archive', {'foo': 'bar'}),
)
-In this example, for a request to ``/blog/2005/``, Django will call the
-``blog.views.year_archive()`` view, passing it these keyword arguments::
-
- year='2005', foo='bar'
+In this example, for a request to ``/blog/2005/``, Django will call
+``blog.views.year_archive(year='2005', foo='bar')``.
This technique is used in the
:doc:`syndication framework </ref/contrib/syndication>` to pass metadata and
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt
index a8c85ddf6e..c4bd15e72e 100644
--- a/docs/topics/http/views.txt
+++ b/docs/topics/http/views.txt
@@ -127,6 +127,8 @@ called ``404.html`` and located in the top level of your template tree.
Customizing error views
=======================
+.. _http_not_found_view:
+
The 404 (page not found) view
-----------------------------
@@ -167,6 +169,8 @@ Four things to note about 404 views:
your 404 view will never be used, and your URLconf will be displayed
instead, with some debug information.
+.. _http_internal_server_error_view:
+
The 500 (server error) view
----------------------------
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index bdbb04823d..9bd53da2b9 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -37,6 +37,13 @@ from your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
controls if Django should implement format localization. See
:doc:`/topics/i18n/formatting` for more details.
+.. note::
+
+ Make sure you've activated translation for your project (the fastest way is
+ to check if :setting:`MIDDLEWARE_CLASSES` includes
+ :mod:`django.middleware.locale.LocaleMiddleware`). If you haven't yet,
+ see :ref:`how-django-discovers-language-preference`.
+
Internationalization: in Python code
====================================
@@ -317,10 +324,10 @@ Model fields and relationships ``verbose_name`` and ``help_text`` option values
For example, to translate the help text of the *name* field in the following
model, do the following::
- from django.utils.translation import ugettext_lazy
+ from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
- name = models.CharField(help_text=ugettext_lazy('This is the help text'))
+ name = models.CharField(help_text=_('This is the help text'))
You can mark names of ``ForeignKey``, ``ManyTomanyField`` or ``OneToOneField``
relationship as translatable by using their ``verbose_name`` options::
@@ -344,14 +351,14 @@ It is recommended to always provide explicit
relying on the fallback English-centric and somewhat naïve determination of
verbose names Django performs bu looking at the model's class name::
- from django.utils.translation import ugettext_lazy
+ from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
- name = models.CharField(_('name'), help_text=ugettext_lazy('This is the help text'))
+ name = models.CharField(_('name'), help_text=_('This is the help text'))
class Meta:
- verbose_name = ugettext_lazy('my thing')
- verbose_name_plural = ugettext_lazy('my things')
+ verbose_name = _('my thing')
+ verbose_name_plural = _('my things')
Model methods ``short_description`` attribute values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -456,6 +463,9 @@ Internationalization: in template code
Translations in :doc:`Django templates </topics/templates>` uses two template
tags and a slightly different syntax than in Python code. To give your template
access to these tags, put ``{% load i18n %}`` toward the top of your template.
+As with all template tags, this tag needs to be loaded in all templates which
+use translations, even those templates that extend from other templates which
+have already loaded the ``i18n`` tag.
.. templatetag:: trans
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index a878d42266..b54a9475ae 100644
--- a/docs/topics/logging.txt
+++ b/docs/topics/logging.txt
@@ -218,10 +218,11 @@ handlers, filters and formatters that you want in your logging setup,
and the log levels and other properties that you want those components
to have.
-Logging is configured immediately after settings have been loaded.
-Since the loading of settings is one of the first things that Django
-does, you can be certain that loggers are always ready for use in your
-project code.
+Logging is configured as soon as settings have been loaded
+(either manually using :func:`~django.conf.settings.configure` or when at least
+one setting is accessed). Since the loading of settings is one of the first
+things that Django does, you can be certain that loggers are always ready for
+use in your project code.
.. _dictConfig format: http://docs.python.org/library/logging.config.html#configuration-dictionary-schema
@@ -515,6 +516,35 @@ logging module.
through the filter. Handling of that record will not proceed if the callback
returns False.
+ For instance, to filter out :class:`~django.http.UnreadablePostError`
+ (raised when a user cancels an upload) from the admin emails, you would
+ create a filter function::
+
+ from django.http import UnreadablePostError
+
+ def skip_unreadable_post(record):
+ if record.exc_info:
+ exc_type, exc_value = record.exc_info[:2]
+ if isinstance(exc_value, UnreadablePostError):
+ return False
+ return True
+
+ and then add it to your logging config::
+
+ 'filters': {
+ 'skip_unreadable_posts': {
+ '()': 'django.utils.log.CallbackFilter',
+ 'callback': skip_unreadable_post,
+ }
+ },
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'filters': ['skip_unreadable_posts'],
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+
.. class:: RequireDebugFalse()
.. versionadded:: 1.4
diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt
index b09c1d2347..457486caa4 100644
--- a/docs/topics/python3.txt
+++ b/docs/topics/python3.txt
@@ -1,25 +1,213 @@
-======================
-Python 3 compatibility
-======================
+===================
+Porting to Python 3
+===================
Django 1.5 is the first version of Django to support Python 3. The same code
runs both on Python 2 (≥ 2.6.5) and Python 3 (≥ 3.2), thanks to the six_
-compatibility layer and ``unicode_literals``.
+compatibility layer.
.. _six: http://packages.python.org/six/
-This document is not meant as a Python 2 to Python 3 migration guide. There
-are many existing resources, including `Python's official porting guide`_.
-Rather, it describes guidelines that apply to Django's code and are
-recommended for pluggable apps that run with both Python 2 and 3.
+This document is primarily targeted at authors of pluggable application
+who want to support both Python 2 and 3. It also describes guidelines that
+apply to Django's code.
+
+Philosophy
+==========
+
+This document assumes that you are familiar with the changes between Python 2
+and Python 3. If you aren't, read `Python's official porting guide`_ first.
+Refreshing your knowledge of unicode handling on Python 2 and 3 will help; the
+`Pragmatic Unicode`_ presentation is a good resource.
+
+Django uses the *Python 2/3 Compatible Source* strategy. Of course, you're
+free to chose another strategy for your own code, especially if you don't need
+to stay compatible with Python 2. But authors of pluggable applications are
+encouraged to use the same porting strategy as Django itself.
+
+Writing compatible code is much easier if you target Python ≥ 2.6. You will
+most likely take advantage of the compatibility functions introduced in Django
+1.5, like :mod:`django.utils.six`, so your application will also require
+Django ≥ 1.5.
+
+Obviously, writing compatible source code adds some overhead, and that can
+cause frustration. Django's developers have found that attempting to write
+Python 3 code that's compatible with Python 2 is much more rewarding than the
+opposite. Not only does that make your code more future-proof, but Python 3's
+advantages (like the saner string handling) start shining quickly. Dealing
+with Python 2 becomes a backwards compatibility requirement, and we as
+developers are used to dealing with such constraints.
+
+Porting tools provided by Django are inspired by this philosophy, and it's
+reflected throughout this guide.
.. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html
+.. _Pragmatic Unicode: http://nedbatchelder.com/text/unipain.html
+
+Porting tips
+============
+
+Unicode literals
+----------------
+
+This step consists in:
+
+- Adding ``from __future__ import unicode_literals`` at the top of your Python
+ modules -- it's best to put it in each and every module, otherwise you'll
+ keep checking the top of your files to see which mode is in effect;
+- Removing the ``u`` prefix before unicode strings;
+- Adding a ``b`` prefix before bytestrings.
+
+Performing these changes systematically guarantees backwards compatibility.
+
+However, Django applications generally don't need bytestrings, since Django
+only exposes unicode interfaces to the programmer. Python 3 discourages using
+bytestrings, except for binary data or byte-oriented interfaces. Python 2
+makes bytestrings and unicode strings effectively interchangeable, as long as
+they only contain ASCII data. Take advantage of this to use unicode strings
+wherever possible and avoid the ``b`` prefixes.
+
+.. note::
+
+ Python 2's ``u`` prefix is a syntax error in Python 3.2 but it will be
+ allowed again in Python 3.3 thanks to :pep:`414`. Thus, this
+ transformation is optional if you target Python ≥ 3.3. It's still
+ recommended, per the "write Python 3 code" philosophy.
+
+String handling
+---------------
+
+Python 2's :class:`unicode` type was renamed :class:`str` in Python 3,
+:class:`str` was renamed :class:`bytes`, and :class:`basestring` disappeared.
+six_ provides :ref:`tools <string-handling-with-six>` to deal with these
+changes.
+
+Django also contains several string related classes and functions in the
+:mod:`django.utils.encoding` and :mod:`django.utils.safestring` modules. Their
+names used the words ``str``, which doesn't mean the same thing in Python 2
+and Python 3, and ``unicode``, which doesn't exist in Python 3. In order to
+avoid ambiguity and confusion these concepts were renamed ``bytes`` and
+``text``.
+
+Here are the name changes in :mod:`django.utils.encoding`:
+
+================== ==================
+Old name New name
+================== ==================
+``smart_str`` ``smart_bytes``
+``smart_unicode`` ``smart_text``
+``force_unicode`` ``force_text``
+================== ==================
+
+For backwards compatibility, the old names still work on Python 2. Under
+Python 3, ``smart_str`` is an alias for ``smart_text``.
+
+.. note::
+
+ :mod:`django.utils.encoding` was deeply refactored in Django 1.5 to
+ provide a more consistent API. Check its documentation for more
+ information.
+
+:mod:`django.utils.safestring` is mostly used via the
+:func:`~django.utils.safestring.mark_safe` and
+:func:`~django.utils.safestring.mark_for_escaping` functions, which didn't
+change. In case you're using the internals, here are the name changes:
+
+================== ==================
+Old name New name
+================== ==================
+``EscapeString`` ``EscapeBytes``
+``EscapeUnicode`` ``EscapeText``
+``SafeString`` ``SafeBytes``
+``SafeUnicode`` ``SafeText``
+================== ==================
+
+For backwards compatibility, the old names still work on Python 2. Under
+Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText``
+and ``SafeText`` respectively.
+
+:meth:`__str__` and :meth:`__unicode__` methods
+-----------------------------------------------
+
+In Python 2, the object model specifies :meth:`__str__` and
+:meth:`__unicode__` methods. If these methods exist, they must return
+:class:`str` (bytes) and :class:`unicode` (text) respectively.
+
+The ``print`` statement and the :func:`str` built-in call :meth:`__str__` to
+determine the human-readable representation of an object. The :func:`unicode`
+built-in calls :meth:`__unicode__` if it exists, and otherwise falls back to
+:meth:`__str__` and decodes the result with the system encoding. Conversely,
+the :class:`~django.db.models.Model` base class automatically derives
+:meth:`__str__` from :meth:`__unicode__` by encoding to UTF-8.
+
+In Python 3, there's simply :meth:`__str__`, which must return :class:`str`
+(text).
+
+(It is also possible to define :meth:`__bytes__`, but Django application have
+little use for that method, because they hardly ever deal with
+:class:`bytes`.)
+
+Django provides a simple way to define :meth:`__str__` and :meth:`__unicode__`
+methods that work on Python 2 and 3: you must define a :meth:`__str__` method
+returning text and to apply the
+:func:`~django.utils.encoding.python_2_unicode_compatible` decorator.
+
+On Python 3, the decorator is a no-op. On Python 2, it defines appropriate
+:meth:`__unicode__` and :meth:`__str__` methods (replacing the original
+:meth:`__str__` method in the process). Here's an example::
+
+ from __future__ import unicode_literals
+ from django.utils.encoding import python_2_unicode_compatible
+
+ @python_2_unicode_compatible
+ class MyClass(object):
+ def __str__(self):
+ return "Instance of my class"
+
+This technique is the best match for Django's porting philosophy.
+
+Finally, note that :meth:`__repr__` must return a :class:`str` on all versions
+of Python.
+
+:class:`dict` and :class:`dict`-like classes
+--------------------------------------------
+
+:meth:`dict.keys`, :meth:`dict.items` and :meth:`dict.values` return lists in
+Python 2 and iterators in Python 3. :class:`~django.http.QueryDict` and the
+:class:`dict`-like classes defined in :mod:`django.utils.datastructures`
+behave likewise in Python 3.
+
+six_ provides compatibility functions to work around this change:
+:func:`~six.iterkeys`, :func:`~six.iteritems`, and :func:`~six.itervalues`.
+Django's bundled version adds :func:`~django.utils.six.iterlists` for
+:class:`~django.utils.datastructures.MultiValueDict` and its subclasses.
+
+:class:`~django.http.HttpRequest` and :class:`~django.http.HttpResponse` objects
+--------------------------------------------------------------------------------
+
+According to :pep:`3333`:
+
+- headers are always :class:`str` objects,
+- input and output streams are always :class:`bytes` objects.
+
+Specifically, :attr:`HttpResponse.content <django.http.HttpResponse.content>`
+contains :class:`bytes`, which may become an issue if you compare it with a
+:class:`str` in your tests. The preferred solution is to rely on
+:meth:`~django.test.TestCase.assertContains` and
+:meth:`~django.test.TestCase.assertNotContains`. These methods accept a
+response and a unicode string as arguments.
+
+Coding guidelines
+=================
+
+The following guidelines are enforced in Django's source code. They're also
+recommended for third-party application who follow the same porting strategy.
Syntax requirements
-===================
+-------------------
Unicode
--------
+~~~~~~~
In Python 3, all strings are considered Unicode by default. The ``unicode``
type from Python 2 is called ``str`` in Python 3, and ``str`` becomes
@@ -36,17 +224,25 @@ In order to enable the same behavior in Python 2, every module must import
my_string = "This is an unicode literal"
my_bytestring = b"This is a bytestring"
-If you need a byte string under Python 2 and a unicode string under Python 3,
-use the :func:`str` builtin::
+If you need a byte string literal under Python 2 and a unicode string literal
+under Python 3, use the :func:`str` builtin::
str('my string')
-Be cautious if you have to `slice bytestrings`_.
+In Python 3, there aren't any automatic conversions between :class:`str` and
+:class:`bytes`, and the :mod:`codecs` module became more strict.
+:meth:`str.decode` always returns :class:`bytes`, and :meth:`bytes.decode`
+always returns :class:`str`. As a consequence, the following pattern is
+sometimes necessary::
+
+ value = value.encode('ascii', 'ignore').decode('ascii')
+
+Be cautious if you have to `index bytestrings`_.
-.. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals
+.. _index bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals
Exceptions
-----------
+~~~~~~~~~~
When you capture exceptions, use the ``as`` keyword::
@@ -59,17 +255,64 @@ This older syntax was removed in Python 3::
try:
...
- except MyException, exc:
+ except MyException, exc: # Don't do that!
...
The syntax to reraise an exception with a different traceback also changed.
Use :func:`six.reraise`.
+Magic methods
+-------------
+
+Use the patterns below to handle magic methods renamed in Python 3.
+
+Iterators
+~~~~~~~~~
+
+::
+
+ class MyIterator(object):
+ def __iter__(self):
+ return self # implement some logic here
+
+ def __next__(self):
+ raise StopIteration # implement some logic here
+
+ next = __next__ # Python 2 compatibility
+
+Boolean evaluation
+~~~~~~~~~~~~~~~~~~
+
+::
+
+ class MyBoolean(object):
+
+ def __bool__(self):
+ return True # implement some logic here
+
+ __nonzero__ = __bool__ # Python 2 compatibility
+
+Division
+~~~~~~~~
+
+::
+
+ class MyDivisible(object):
+
+ def __truediv__(self, other):
+ return self / other # implement some logic here
+
+ __div__ = __truediv__ # Python 2 compatibility
+
+ def __itruediv__(self, other):
+ return self // other # implement some logic here
+
+ __idiv__ = __itruediv__ # Python 2 compatibility
.. module: django.utils.six
Writing compatible code with six
-================================
+--------------------------------
six_ is the canonical compatibility library for supporting Python 2 and 3 in
a single codebase. Read its documentation!
@@ -78,8 +321,10 @@ a single codebase. Read its documentation!
Here are the most common changes required to write compatible code.
-String types
-------------
+.. _string-handling-with-six:
+
+String handling
+~~~~~~~~~~~~~~~
The ``basestring`` and ``unicode`` types were removed in Python 3, and the
meaning of ``str`` changed. To test these types, use the following idioms::
@@ -92,7 +337,7 @@ Python ≥ 2.6 provides ``bytes`` as an alias for ``str``, so you don't need
:attr:`six.binary_type`.
``long``
---------
+~~~~~~~~
The ``long`` type no longer exists in Python 3. ``1L`` is a syntax error. Use
:data:`six.integer_types` check if a value is an integer or a long::
@@ -100,21 +345,27 @@ The ``long`` type no longer exists in Python 3. ``1L`` is a syntax error. Use
isinstance(myvalue, six.integer_types) # replacement for (int, long)
``xrange``
-----------
+~~~~~~~~~~
Import :func:`six.moves.xrange` wherever you use ``xrange``.
Moved modules
--------------
+~~~~~~~~~~~~~
Some modules were renamed in Python 3. The :mod:`django.utils.six.moves
<six.moves>` module provides a compatible location to import them.
-In addition to six' defaults, Django's version provides ``dummy_thread`` as
-``_dummy_thread``.
+The ``urllib``, ``urllib2`` and ``urlparse`` modules were reworked in depth
+and :mod:`django.utils.six.moves <six.moves>` doesn't handle them. Django
+explicitly tries both locations, as follows::
+
+ try:
+ from urllib.parse import urlparse, urlunparse
+ except ImportError: # Python 2
+ from urlparse import urlparse, urlunparse
PY3
----
+~~~
If you need different code in Python 2 and Python 3, check :data:`six.PY3`::
@@ -129,9 +380,9 @@ function.
.. module:: django.utils.six
Customizations of six
-=====================
+---------------------
-The version of six bundled with Django includes a few additional tools:
+The version of six bundled with Django includes one extra function:
.. function:: iterlists(MultiValueDict)
@@ -140,3 +391,6 @@ The version of six bundled with Django includes a few additional tools:
:meth:`~django.utils.datastructures.MultiValueDict.iterlists()` on Python
2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on
Python 3.
+
+In addition to six' defaults moves, Django's version provides ``thread`` as
+``_thread`` and ``dummy_thread`` as ``_dummy_thread``.
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 2fa6643975..af45a8d95b 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -102,7 +102,7 @@ Use a dot (``.``) to access attributes of a variable.
attempts to loop over a ``collections.defaultdict``::
{% for k, v in defaultdict.iteritems %}
- Do something with k and v here...
+ Do something with k and v here...
{% endfor %}
Because dictionary lookup happens first, that behavior kicks in and provides
@@ -116,6 +116,10 @@ If you use a variable that doesn't exist, the template system will insert
the value of the :setting:`TEMPLATE_STRING_IF_INVALID` setting, which is set
to ``''`` (the empty string) by default.
+Note that "bar" in a template expression like ``{{ foo.bar }}`` will be
+interpreted as a literal string and not using the value of the variable "bar",
+if one exists in the template context.
+
Filters
=======
diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
index 1f4c970d3e..c4c73733f5 100644
--- a/docs/topics/testing.txt
+++ b/docs/topics/testing.txt
@@ -2039,7 +2039,7 @@ out the `full reference`_ for more details.
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
- lambda driver: driver.find_element_by_tag_name('body'), timeout=10)
+ lambda driver: driver.find_element_by_tag_name('body'))
The tricky thing here is that there's really no such thing as a "page load,"
especially in modern Web apps that generate HTML dynamically after the
diff --git a/setup.cfg b/setup.cfg
index 37cc9a592f..e189ffb47b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,4 +1,4 @@
[bdist_rpm]
-doc_files = docs extras AUTHORS INSTALL LICENSE README
+doc_files = docs extras AUTHORS INSTALL LICENSE README.rst
install-script = scripts/rpm-install.sh
diff --git a/tests/modeltests/aggregation/models.py b/tests/modeltests/aggregation/models.py
index ccc12898b7..b4f797ee03 100644
--- a/tests/modeltests/aggregation/models.py
+++ b/tests/modeltests/aggregation/models.py
@@ -1,22 +1,26 @@
# coding: utf-8
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)
age = models.IntegerField()
friends = models.ManyToManyField('self', blank=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Publisher(models.Model):
name = models.CharField(max_length=255)
num_awards = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Book(models.Model):
isbn = models.CharField(max_length=9)
name = models.CharField(max_length=255)
@@ -28,15 +32,16 @@ class Book(models.Model):
publisher = models.ForeignKey(Publisher)
pubdate = models.DateField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Store(models.Model):
name = models.CharField(max_length=255)
books = models.ManyToManyField(Book)
original_opening = models.DateTimeField()
friday_night_closing = models.TimeField()
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
index 06aa9cf3c3..660beddf49 100644
--- a/tests/modeltests/basic/models.py
+++ b/tests/modeltests/basic/models.py
@@ -5,8 +5,10 @@
This is a basic model with only two non-primary-key fields.
"""
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, default='Default headline')
pub_date = models.DateTimeField()
@@ -14,5 +16,5 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date','headline')
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
index ee01911573..2fa33a9680 100644
--- a/tests/modeltests/choices/models.py
+++ b/tests/modeltests/choices/models.py
@@ -10,6 +10,7 @@ field. This method returns the "human-readable" value of the field.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
GENDER_CHOICES = (
@@ -17,9 +18,10 @@ GENDER_CHOICES = (
('F', 'Female'),
)
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=20)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py
index 39f1274a8f..16f0563d6b 100644
--- a/tests/modeltests/custom_columns/models.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -18,24 +18,27 @@ from the default generated name, use the ``db_table`` parameter on the
from __future__ 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):
first_name = models.CharField(max_length=30, db_column='firstname')
last_name = models.CharField(max_length=30, db_column='last')
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
class Meta:
db_table = 'my_author_table'
ordering = ('last_name','first_name')
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, db_table='my_m2m_table')
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
index a9845ad414..de7c1772ed 100644
--- a/tests/modeltests/custom_managers/models.py
+++ b/tests/modeltests/custom_managers/models.py
@@ -12,6 +12,7 @@ returns.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# An example of a custom manager called "objects".
@@ -19,13 +20,14 @@ class PersonManager(models.Manager):
def get_fun_people(self):
return self.filter(fun=True)
+@python_2_unicode_compatible
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
fun = models.BooleanField()
objects = PersonManager()
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
# An example of a custom manager that sets get_query_set().
@@ -34,6 +36,7 @@ class PublishedBookManager(models.Manager):
def get_query_set(self):
return super(PublishedBookManager, self).get_query_set().filter(is_published=True)
+@python_2_unicode_compatible
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=30)
@@ -41,7 +44,7 @@ class Book(models.Model):
published_objects = PublishedBookManager()
authors = models.ManyToManyField(Person, related_name='books')
- def __unicode__(self):
+ def __str__(self):
return self.title
# An example of providing multiple custom managers.
@@ -50,6 +53,7 @@ class FastCarManager(models.Manager):
def get_query_set(self):
return super(FastCarManager, self).get_query_set().filter(top_speed__gt=150)
+@python_2_unicode_compatible
class Car(models.Model):
name = models.CharField(max_length=10)
mileage = models.IntegerField()
@@ -57,5 +61,5 @@ class Car(models.Model):
cars = models.Manager()
fast_cars = FastCarManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
index 4e3da58851..cef3fd722b 100644
--- a/tests/modeltests/custom_methods/models.py
+++ b/tests/modeltests/custom_methods/models.py
@@ -7,13 +7,15 @@ Any method you add to a model will be available to instances.
import datetime
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()
- def __unicode__(self):
+ def __str__(self):
return self.headline
def was_published_today(self):
diff --git a/tests/modeltests/custom_pk/fields.py b/tests/modeltests/custom_pk/fields.py
index 68fb9dcd16..92096a0ff7 100644
--- a/tests/modeltests/custom_pk/fields.py
+++ b/tests/modeltests/custom_pk/fields.py
@@ -3,8 +3,10 @@ import string
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class MyWrapper(object):
def __init__(self, value):
self.value = value
@@ -12,7 +14,7 @@ class MyWrapper(object):
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.value)
- def __unicode__(self):
+ def __str__(self):
return self.value
def __eq__(self, other):
@@ -20,8 +22,7 @@ class MyWrapper(object):
return self.value == other.value
return self.value == other
-class MyAutoField(models.CharField):
- __metaclass__ = models.SubfieldBase
+class MyAutoField(six.with_metaclass(models.SubfieldBase, models.CharField)):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 10
@@ -30,7 +31,7 @@ class MyAutoField(models.CharField):
def pre_save(self, instance, add):
value = getattr(instance, self.attname, None)
if not value:
- value = MyWrapper(''.join(random.sample(string.lowercase, 10)))
+ value = MyWrapper(''.join(random.sample(string.ascii_lowercase, 10)))
setattr(instance, self.attname, value)
return value
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
index 8199b05a1a..5ef9b69f0c 100644
--- a/tests/modeltests/custom_pk/models.py
+++ b/tests/modeltests/custom_pk/models.py
@@ -11,8 +11,10 @@ from __future__ import absolute_import, unicode_literals
from django.db import models
from .fields import MyAutoField
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Employee(models.Model):
employee_code = models.IntegerField(primary_key=True, db_column = 'code')
first_name = models.CharField(max_length=20)
@@ -20,22 +22,24 @@ class Employee(models.Model):
class Meta:
ordering = ('last_name', 'first_name')
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Business(models.Model):
name = models.CharField(max_length=20, primary_key=True)
employees = models.ManyToManyField(Employee)
class Meta:
verbose_name_plural = 'businesses'
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Bar(models.Model):
id = MyAutoField(primary_key=True, db_index=True)
- def __unicode__(self):
+ def __str__(self):
return repr(self.pk)
diff --git a/tests/modeltests/defer/models.py b/tests/modeltests/defer/models.py
index c64becf972..0688cbc984 100644
--- a/tests/modeltests/defer/models.py
+++ b/tests/modeltests/defer/models.py
@@ -3,18 +3,20 @@ Tests for defer() and only().
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class Secondary(models.Model):
first = models.CharField(max_length=50)
second = models.CharField(max_length=50)
+@python_2_unicode_compatible
class Primary(models.Model):
name = models.CharField(max_length=50)
value = models.CharField(max_length=50)
related = models.ForeignKey(Secondary)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Child(Primary):
diff --git a/tests/modeltests/delete/models.py b/tests/modeltests/delete/models.py
index f8b78eb7b7..e0cec426ea 100644
--- a/tests/modeltests/delete/models.py
+++ b/tests/modeltests/delete/models.py
@@ -1,6 +1,10 @@
+from __future__ import unicode_literals
+
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class R(models.Model):
is_default = models.BooleanField(default=False)
diff --git a/tests/modeltests/distinct_on_fields/models.py b/tests/modeltests/distinct_on_fields/models.py
index 33665e9624..7982f435d0 100644
--- a/tests/modeltests/distinct_on_fields/models.py
+++ b/tests/modeltests/distinct_on_fields/models.py
@@ -1,7 +1,9 @@
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Tag(models.Model):
name = models.CharField(max_length=10)
parent = models.ForeignKey('self', blank=True, null=True,
@@ -10,19 +12,21 @@ class Tag(models.Model):
class Meta:
ordering = ['name']
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Celebrity(models.Model):
name = models.CharField("Name", max_length=20)
greatest_fan = models.ForeignKey("Fan", null=True, unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Fan(models.Model):
fan_of = models.ForeignKey(Celebrity)
+@python_2_unicode_compatible
class Staff(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=50)
@@ -30,12 +34,13 @@ class Staff(models.Model):
tags = models.ManyToManyField(Tag, through='StaffTag')
coworkers = models.ManyToManyField('self')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class StaffTag(models.Model):
staff = models.ForeignKey(Staff)
tag = models.ForeignKey(Tag)
- def __unicode__(self):
+ def __str__(self):
return "%s -> %s" % (self.tag, self.staff)
diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py
index 018a0cf795..f592a0eb13 100644
--- a/tests/modeltests/expressions/models.py
+++ b/tests/modeltests/expressions/models.py
@@ -4,15 +4,18 @@ Tests for F() query expression syntax.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Employee(models.Model):
firstname = models.CharField(max_length=50)
lastname = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.firstname, self.lastname)
+@python_2_unicode_compatible
class Company(models.Model):
name = models.CharField(max_length=100)
num_employees = models.PositiveIntegerField()
@@ -25,5 +28,5 @@ class Company(models.Model):
related_name='company_point_of_contact_set',
null=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
index 18840c8b4b..c99d4871cd 100644
--- a/tests/modeltests/field_defaults/models.py
+++ b/tests/modeltests/field_defaults/models.py
@@ -13,11 +13,13 @@ field.
from datetime import datetime
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, default='Default headline')
pub_date = models.DateTimeField(default=datetime.now)
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py
index 0d4ff98aa7..a3867e3671 100644
--- a/tests/modeltests/field_subclassing/fields.py
+++ b/tests/modeltests/field_subclassing/fields.py
@@ -5,8 +5,10 @@ import json
from django.db import models
from django.utils.encoding import force_text
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Small(object):
"""
A simple class to show that non-trivial Python objects can be used as
@@ -15,19 +17,15 @@ class Small(object):
def __init__(self, first, second):
self.first, self.second = first, second
- def __unicode__(self):
- return '%s%s' % (force_text(self.first), force_text(self.second))
-
def __str__(self):
- return six.text_type(self).encode('utf-8')
+ return '%s%s' % (force_text(self.first), force_text(self.second))
-class SmallField(models.Field):
+class SmallField(six.with_metaclass(models.SubfieldBase, models.Field)):
"""
Turns the "Small" class into a Django field. Because of the similarities
with normal character fields and the fact that Small.__unicode__ does
something sensible, we don't need to implement a lot here.
"""
- __metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2
@@ -57,8 +55,7 @@ class SmallerField(SmallField):
pass
-class JSONField(models.TextField):
- __metaclass__ = models.SubfieldBase
+class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)):
description = ("JSONField automatically serializes and desializes values to "
"and from JSON.")
diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py
index 2df9664cdc..642573cc83 100644
--- a/tests/modeltests/field_subclassing/models.py
+++ b/tests/modeltests/field_subclassing/models.py
@@ -8,13 +8,15 @@ from django.db import models
from django.utils.encoding import force_text
from .fields import SmallField, SmallerField, JSONField
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class MyModel(models.Model):
name = models.CharField(max_length=10)
data = SmallField('small field')
- def __unicode__(self):
+ def __str__(self):
return force_text(self.name)
class OtherModel(models.Model):
diff --git a/tests/modeltests/files/tests.py b/tests/modeltests/files/tests.py
index 565d4c8942..71ca103205 100644
--- a/tests/modeltests/files/tests.py
+++ b/tests/modeltests/files/tests.py
@@ -20,7 +20,7 @@ class FileStorageTests(TestCase):
shutil.rmtree(temp_storage_location)
def test_files(self):
- temp_storage.save('tests/default.txt', ContentFile(b'default content'))
+ temp_storage.save('tests/default.txt', ContentFile('default content'))
# Attempting to access a FileField from the class raises a descriptive
# error
self.assertRaises(AttributeError, lambda: Storage.normal)
@@ -31,7 +31,7 @@ class FileStorageTests(TestCase):
self.assertRaises(ValueError, lambda: obj1.normal.size)
# Saving a file enables full functionality.
- obj1.normal.save("django_test.txt", ContentFile(b"content"))
+ obj1.normal.save("django_test.txt", ContentFile("content"))
self.assertEqual(obj1.normal.name, "tests/django_test.txt")
self.assertEqual(obj1.normal.size, 7)
self.assertEqual(obj1.normal.read(), b"content")
@@ -59,7 +59,7 @@ class FileStorageTests(TestCase):
# Save another file with the same name.
obj2 = Storage()
- obj2.normal.save("django_test.txt", ContentFile(b"more content"))
+ obj2.normal.save("django_test.txt", ContentFile("more content"))
self.assertEqual(obj2.normal.name, "tests/django_test_1.txt")
self.assertEqual(obj2.normal.size, 12)
@@ -70,13 +70,13 @@ class FileStorageTests(TestCase):
# Deleting an object does not delete the file it uses.
obj2.delete()
- obj2.normal.save("django_test.txt", ContentFile(b"more content"))
+ obj2.normal.save("django_test.txt", ContentFile("more content"))
self.assertEqual(obj2.normal.name, "tests/django_test_2.txt")
# Multiple files with the same name get _N appended to them.
objs = [Storage() for i in range(3)]
for o in objs:
- o.normal.save("multiple_files.txt", ContentFile(b"Same Content"))
+ o.normal.save("multiple_files.txt", ContentFile("Same Content"))
self.assertEqual(
[o.normal.name for o in objs],
["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"]
@@ -100,7 +100,7 @@ class FileStorageTests(TestCase):
# Verify the fix for #5655, making sure the directory is only
# determined once.
obj4 = Storage()
- obj4.random.save("random_file", ContentFile(b"random content"))
+ obj4.random.save("random_file", ContentFile("random content"))
self.assertTrue(obj4.random.name.endswith("/random_file"))
def test_max_length(self):
@@ -135,6 +135,6 @@ class FileTests(unittest.TestCase):
def test_file_mode(self):
# Should not set mode to None if it is not present.
# See #14681, stdlib gzip module crashes if mode is set to None
- file = SimpleUploadedFile("mode_test.txt", "content")
+ file = SimpleUploadedFile("mode_test.txt", b"content")
self.assertFalse(hasattr(file, 'mode'))
g = gzip.GzipFile(fileobj=file)
diff --git a/tests/modeltests/fixtures/models.py b/tests/modeltests/fixtures/models.py
index 586f65e60f..8bd3501926 100644
--- a/tests/modeltests/fixtures/models.py
+++ b/tests/modeltests/fixtures/models.py
@@ -12,38 +12,43 @@ from django.contrib.auth.models import Permission
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
+@python_2_unicode_compatible
class Category(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
- def __unicode__(self):
+ def __str__(self):
return self.title
class Meta:
ordering = ('title',)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100, default='Default headline')
pub_date = models.DateTimeField()
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
ordering = ('-pub_date', 'headline')
+@python_2_unicode_compatible
class Blog(models.Model):
name = models.CharField(max_length=100)
featured = models.ForeignKey(Article, related_name='fixtures_featured_set')
articles = models.ManyToManyField(Article, blank=True,
related_name='fixtures_articles_set')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Tag(models.Model):
name = models.CharField(max_length=100)
tagged_type = models.ForeignKey(ContentType, related_name="fixtures_tag_set")
@@ -51,7 +56,7 @@ class Tag(models.Model):
tagged = generic.GenericForeignKey(ct_field='tagged_type',
fk_field='tagged_id')
- def __unicode__(self):
+ def __str__(self):
return '<%s: %s> tagged "%s"' % (self.tagged.__class__.__name__,
self.tagged, self.name)
@@ -59,10 +64,11 @@ class PersonManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
+@python_2_unicode_compatible
class Person(models.Model):
objects = PersonManager()
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
@@ -79,19 +85,21 @@ class Spy(Person):
objects = SpyManager()
cover_blown = models.BooleanField(default=False)
+@python_2_unicode_compatible
class Visa(models.Model):
person = models.ForeignKey(Person)
permissions = models.ManyToManyField(Permission, blank=True)
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.person.name,
', '.join(p.name for p in self.permissions.all()))
+@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Person)
- def __unicode__(self):
+ def __str__(self):
authors = ' and '.join(a.name for a in self.authors.all())
return '%s by %s' % (self.name, authors) if authors else self.name
diff --git a/tests/modeltests/fixtures_model_package/models/__init__.py b/tests/modeltests/fixtures_model_package/models/__init__.py
index d309165e9c..deeba48aa9 100644
--- a/tests/modeltests/fixtures_model_package/models/__init__.py
+++ b/tests/modeltests/fixtures_model_package/models/__init__.py
@@ -1,11 +1,13 @@
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, default='Default headline')
pub_date = models.DateTimeField()
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index f2dcf7db24..2f025e660b 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -14,8 +14,10 @@ from __future__ import unicode_literals
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
+@python_2_unicode_compatible
class TaggedItem(models.Model):
"""A tag on an item."""
tag = models.SlugField()
@@ -27,12 +29,13 @@ class TaggedItem(models.Model):
class Meta:
ordering = ["tag", "content_type__name"]
- def __unicode__(self):
+ def __str__(self):
return self.tag
class ValuableTaggedItem(TaggedItem):
value = models.PositiveIntegerField()
+@python_2_unicode_compatible
class Comparison(models.Model):
"""
A model that tests having multiple GenericForeignKeys
@@ -48,9 +51,10 @@ class Comparison(models.Model):
first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2")
- def __unicode__(self):
+ def __str__(self):
return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj)
+@python_2_unicode_compatible
class Animal(models.Model):
common_name = models.CharField(max_length=150)
latin_name = models.CharField(max_length=150)
@@ -60,25 +64,27 @@ class Animal(models.Model):
object_id_field="object_id1",
content_type_field="content_type1")
- def __unicode__(self):
+ def __str__(self):
return self.common_name
+@python_2_unicode_compatible
class Vegetable(models.Model):
name = models.CharField(max_length=150)
is_yucky = models.BooleanField(default=True)
tags = generic.GenericRelation(TaggedItem)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Mineral(models.Model):
name = models.CharField(max_length=150)
hardness = models.PositiveSmallIntegerField()
# note the lack of an explicit GenericRelation here...
- def __unicode__(self):
+ def __str__(self):
return self.name
class GeckoManager(models.Manager):
diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
index d8a690f48c..fe594dd802 100644
--- a/tests/modeltests/get_latest/models.py
+++ b/tests/modeltests/get_latest/models.py
@@ -9,8 +9,10 @@ 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()
@@ -18,14 +20,15 @@ class Article(models.Model):
class Meta:
get_latest_by = 'pub_date'
- def __unicode__(self):
+ 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 __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/modeltests/get_object_or_404/models.py
index f0c73ed94a..bda060569e 100644
--- a/tests/modeltests/get_object_or_404/models.py
+++ b/tests/modeltests/get_object_or_404/models.py
@@ -11,23 +11,26 @@ performing a ``filter()`` lookup and raising a ``Http404`` exception if a
"""
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=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
class ArticleManager(models.Manager):
def get_query_set(self):
return super(ArticleManager, self).get_query_set().filter(authors__name__icontains='sir')
+@python_2_unicode_compatible
class Article(models.Model):
authors = models.ManyToManyField(Author)
title = models.CharField(max_length=50)
objects = models.Manager()
by_a_sir = ArticleManager()
- def __unicode__(self):
+ def __str__(self):
return self.title
diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py
index 78b92f09df..678f5a401c 100644
--- a/tests/modeltests/get_or_create/models.py
+++ b/tests/modeltests/get_or_create/models.py
@@ -9,14 +9,16 @@ parameters.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthday = models.DateField()
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
class ManualPrimaryKeyTest(models.Model):
diff --git a/tests/modeltests/invalid_models/tests.py b/tests/modeltests/invalid_models/tests.py
index 9d7adb045c..e1fc68743e 100644
--- a/tests/modeltests/invalid_models/tests.py
+++ b/tests/modeltests/invalid_models/tests.py
@@ -1,11 +1,11 @@
import copy
import sys
-from io import BytesIO
from django.core.management.validation import get_validation_errors
from django.db.models.loading import cache, load_app
from django.utils import unittest
+from django.utils.six import StringIO
class InvalidModelTestCase(unittest.TestCase):
@@ -16,7 +16,7 @@ class InvalidModelTestCase(unittest.TestCase):
# coloring attached (makes matching the results easier). We restore
# sys.stderr afterwards.
self.old_stdout = sys.stdout
- self.stdout = BytesIO()
+ self.stdout = StringIO()
sys.stdout = self.stdout
# This test adds dummy applications to the app cache. These
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
index b685750347..f388ddf403 100644
--- a/tests/modeltests/lookup/models.py
+++ b/tests/modeltests/lookup/models.py
@@ -8,6 +8,7 @@ from __future__ import unicode_literals
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class Author(models.Model):
@@ -15,6 +16,7 @@ class Author(models.Model):
class Meta:
ordering = ('name', )
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
@@ -22,7 +24,7 @@ class Article(models.Model):
class Meta:
ordering = ('-pub_date', 'headline')
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Tag(models.Model):
@@ -31,24 +33,27 @@ class Tag(models.Model):
class Meta:
ordering = ('name', )
+@python_2_unicode_compatible
class Season(models.Model):
year = models.PositiveSmallIntegerField()
gt = models.IntegerField(null=True, blank=True)
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.year)
+@python_2_unicode_compatible
class Game(models.Model):
season = models.ForeignKey(Season, related_name='games')
home = models.CharField(max_length=100)
away = models.CharField(max_length=100)
- def __unicode__(self):
+ 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)
games = models.ManyToManyField(Game, related_name='players')
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
index 92ed3fbcd9..99c7b01017 100644
--- a/tests/modeltests/m2m_and_m2o/models.py
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -7,17 +7,19 @@ from __future__ import unicode_literals
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class User(models.Model):
username = models.CharField(max_length=20)
+@python_2_unicode_compatible
class Issue(models.Model):
num = models.IntegerField()
cc = models.ManyToManyField(User, blank=True, related_name='test_issue_cc')
client = models.ForeignKey(User, related_name='test_issue_client')
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.num)
class Meta:
diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py
index 85786e8458..e9ae422afb 100644
--- a/tests/modeltests/m2m_intermediary/models.py
+++ b/tests/modeltests/m2m_intermediary/models.py
@@ -12,27 +12,31 @@ field, which specifies the ``Reporter``'s position for the given article
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
- def __unicode__(self):
+ def __str__(self):
return self.headline
+@python_2_unicode_compatible
class Writer(models.Model):
reporter = models.ForeignKey(Reporter)
article = models.ForeignKey(Article)
position = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return '%s (%s)' % (self.reporter, self.position)
diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py
index 3efe7108b9..c2e3b030a4 100644
--- a/tests/modeltests/m2m_multiple/models.py
+++ b/tests/modeltests/m2m_multiple/models.py
@@ -8,16 +8,19 @@ Set ``related_name`` to designate what the reverse relationship is called.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=50)
pub_date = models.DateTimeField()
@@ -26,6 +29,6 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date',)
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
index 83c943ae60..b69930208c 100644
--- a/tests/modeltests/m2m_recursive/models.py
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -17,12 +17,14 @@ appropriate.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=20)
friends = models.ManyToManyField('self')
idols = models.ManyToManyField('self', symmetrical=False, related_name='stalkers')
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/m2m_signals/models.py b/tests/modeltests/m2m_signals/models.py
index 526c4a782e..e997d87f21 100644
--- a/tests/modeltests/m2m_signals/models.py
+++ b/tests/modeltests/m2m_signals/models.py
@@ -1,15 +1,18 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Part(models.Model):
name = models.CharField(max_length=20)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Car(models.Model):
name = models.CharField(max_length=20)
default_parts = models.ManyToManyField(Part)
@@ -18,12 +21,13 @@ class Car(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
class SportsCar(Car):
price = models.IntegerField()
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=20)
fans = models.ManyToManyField('self', related_name='idols', symmetrical=False)
@@ -32,5 +36,5 @@ class Person(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/m2m_through/models.py b/tests/modeltests/m2m_through/models.py
index aa71a049cb..a896f6d9ff 100644
--- a/tests/modeltests/m2m_through/models.py
+++ b/tests/modeltests/m2m_through/models.py
@@ -1,18 +1,21 @@
from datetime import datetime
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# M2M described on one of the models
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=128)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
@@ -22,9 +25,10 @@ class Group(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
@@ -34,16 +38,17 @@ class Membership(models.Model):
class Meta:
ordering = ('date_joined', 'invite_reason', 'group')
- def __unicode__(self):
+ def __str__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)
+@python_2_unicode_compatible
class CustomMembership(models.Model):
person = models.ForeignKey(Person, db_column="custom_person_column", related_name="custom_person_related_name")
group = models.ForeignKey(Group)
weird_fk = models.ForeignKey(Membership, null=True)
date_joined = models.DateTimeField(default=datetime.now)
- def __unicode__(self):
+ def __str__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)
class Meta:
@@ -54,11 +59,12 @@ class TestNoDefaultsOrNulls(models.Model):
group = models.ForeignKey(Group)
nodefaultnonull = models.CharField(max_length=5)
+@python_2_unicode_compatible
class PersonSelfRefM2M(models.Model):
name = models.CharField(max_length=5)
friends = models.ManyToManyField('self', through="Friendship", symmetrical=False)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Friendship(models.Model):
diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py
index c0a4bdeec2..2775d713dd 100644
--- a/tests/modeltests/m2o_recursive/models.py
+++ b/tests/modeltests/m2o_recursive/models.py
@@ -11,19 +11,22 @@ Set ``related_name`` to designate what the reverse relationship is called.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
parent = models.ForeignKey('self', blank=True, null=True, related_name='child_set')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Person(models.Model):
full_name = models.CharField(max_length=20)
mother = models.ForeignKey('self', null=True, related_name='mothers_child_set')
father = models.ForeignKey('self', null=True, related_name='fathers_child_set')
- def __unicode__(self):
+ def __str__(self):
return self.full_name
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index 5076e35653..a196c85092 100644
--- a/tests/modeltests/many_to_many/models.py
+++ b/tests/modeltests/many_to_many/models.py
@@ -8,22 +8,25 @@ objects, and a ``Publication`` has multiple ``Article`` objects.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Publication(models.Model):
title = models.CharField(max_length=30)
- def __unicode__(self):
+ def __str__(self):
return self.title
class Meta:
ordering = ('title',)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index 0d2688e8a4..4e2ed67eea 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -6,22 +6,25 @@ To define a many-to-one relationship, use ``ForeignKey()``.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter)
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py
index be7e650c65..e00ca85928 100644
--- a/tests/modeltests/many_to_one_null/models.py
+++ b/tests/modeltests/many_to_one_null/models.py
@@ -6,14 +6,17 @@ To define a many-to-one relationship that can have a null foreign key, use
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Reporter(models.Model):
name = models.CharField(max_length=30)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
reporter = models.ForeignKey(Reporter, null=True)
@@ -21,5 +24,5 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 8942b21f73..90b019b611 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -14,6 +14,7 @@ import tempfile
from django.core.files.storage import FileSystemStorage
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
temp_storage_dir = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR'])
@@ -31,23 +32,29 @@ ARTICLE_STATUS_CHAR = (
('l', 'Live'),
)
+@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
slug = models.SlugField(max_length=20)
url = models.CharField('The URL', max_length=40)
- def __unicode__(self):
+ def __str__(self):
return self.name
+ def __repr__(self):
+ 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 __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=50)
slug = models.SlugField()
@@ -64,7 +71,7 @@ class Article(models.Model):
self.created = datetime.date.today()
return super(Article, self).save()
- def __unicode__(self):
+ def __str__(self):
return self.headline
class ImprovedArticle(models.Model):
@@ -76,26 +83,29 @@ class ImprovedArticleWithParentLink(models.Model):
class BetterWriter(Writer):
score = models.IntegerField()
+@python_2_unicode_compatible
class WriterProfile(models.Model):
writer = models.OneToOneField(Writer, primary_key=True)
age = models.PositiveIntegerField()
- def __unicode__(self):
+ def __str__(self):
return "%s is %s" % (self.writer, self.age)
from django.contrib.localflavor.us.models import PhoneNumberField
+@python_2_unicode_compatible
class PhoneNumber(models.Model):
phone = PhoneNumberField()
description = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return self.phone
+@python_2_unicode_compatible
class TextFile(models.Model):
description = models.CharField(max_length=20)
file = models.FileField(storage=temp_storage, upload_to='tests', max_length=15)
- def __unicode__(self):
+ def __str__(self):
return self.description
try:
@@ -110,6 +120,7 @@ try:
test_images = True
+ @python_2_unicode_compatible
class ImageFile(models.Model):
def custom_upload_path(self, filename):
path = self.path or 'tests'
@@ -125,9 +136,10 @@ try:
width_field='width', height_field='height')
path = models.CharField(max_length=16, blank=True, default='')
- def __unicode__(self):
+ def __str__(self):
return self.description
+ @python_2_unicode_compatible
class OptionalImageFile(models.Model):
def custom_upload_path(self, filename):
path = self.path or 'tests'
@@ -141,28 +153,31 @@ try:
height = models.IntegerField(editable=False, null=True)
path = models.CharField(max_length=16, blank=True, default='')
- def __unicode__(self):
+ def __str__(self):
return self.description
except ImportError:
test_images = False
+@python_2_unicode_compatible
class CommaSeparatedInteger(models.Model):
field = models.CommaSeparatedIntegerField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return self.field
+@python_2_unicode_compatible
class Product(models.Model):
slug = models.SlugField(unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.slug
+@python_2_unicode_compatible
class Price(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveIntegerField()
- def __unicode__(self):
+ def __str__(self):
return "%s for %s" % (self.quantity, self.price)
class Meta:
@@ -171,6 +186,7 @@ class Price(models.Model):
class ArticleStatus(models.Model):
status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
+@python_2_unicode_compatible
class Inventory(models.Model):
barcode = models.PositiveIntegerField(unique=True)
parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
@@ -179,9 +195,12 @@ class Inventory(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+ def __repr__(self):
+ return self.__str__()
+
class Book(models.Model):
title = models.CharField(max_length=40)
author = models.ForeignKey(Writer, blank=True, null=True)
@@ -202,31 +221,34 @@ class BookXtra(models.Model):
class DerivedBook(Book, BookXtra):
pass
+@python_2_unicode_compatible
class ExplicitPK(models.Model):
key = models.CharField(max_length=20, primary_key=True)
desc = models.CharField(max_length=20, blank=True, unique=True)
class Meta:
unique_together = ('key', 'desc')
- def __unicode__(self):
+ def __str__(self):
return self.key
+@python_2_unicode_compatible
class Post(models.Model):
title = models.CharField(max_length=50, unique_for_date='posted', blank=True)
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()
- def __unicode__(self):
+ def __str__(self):
return self.name
class DerivedPost(Post):
pass
+@python_2_unicode_compatible
class BigInt(models.Model):
biggie = models.BigIntegerField()
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.biggie)
class MarkupField(models.CharField):
diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py
index 1da7f583be..038ce32287 100644
--- a/tests/modeltests/model_forms/tests.py
+++ b/tests/modeltests/model_forms/tests.py
@@ -179,14 +179,14 @@ class PriceFormWithoutQuantity(forms.ModelForm):
class ModelFormBaseTest(TestCase):
def test_base_form(self):
- self.assertEqual(BaseCategoryForm.base_fields.keys(),
+ self.assertEqual(list(BaseCategoryForm.base_fields),
['name', 'slug', 'url'])
def test_extra_fields(self):
class ExtraFields(BaseCategoryForm):
some_extra_field = forms.BooleanField()
- self.assertEqual(ExtraFields.base_fields.keys(),
+ self.assertEqual(list(ExtraFields.base_fields),
['name', 'slug', 'url', 'some_extra_field'])
def test_replace_field(self):
@@ -215,7 +215,7 @@ class ModelFormBaseTest(TestCase):
model = Category
fields = ['url']
- self.assertEqual(LimitFields.base_fields.keys(),
+ self.assertEqual(list(LimitFields.base_fields),
['url'])
def test_exclude_fields(self):
@@ -224,7 +224,7 @@ class ModelFormBaseTest(TestCase):
model = Category
exclude = ['url']
- self.assertEqual(ExcludeFields.base_fields.keys(),
+ self.assertEqual(list(ExcludeFields.base_fields),
['name', 'slug'])
def test_confused_form(self):
@@ -237,7 +237,7 @@ class ModelFormBaseTest(TestCase):
fields = ['name', 'url']
exclude = ['url']
- self.assertEqual(ConfusedForm.base_fields.keys(),
+ self.assertEqual(list(ConfusedForm.base_fields),
['name'])
def test_mixmodel_form(self):
@@ -254,13 +254,13 @@ class ModelFormBaseTest(TestCase):
# overrides BaseCategoryForm.Meta.
self.assertEqual(
- MixModelForm.base_fields.keys(),
+ list(MixModelForm.base_fields),
['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
)
def test_article_form(self):
self.assertEqual(
- ArticleForm.base_fields.keys(),
+ list(ArticleForm.base_fields),
['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
)
@@ -270,7 +270,7 @@ class ModelFormBaseTest(TestCase):
pass
self.assertEqual(
- BadForm.base_fields.keys(),
+ list(BadForm.base_fields),
['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
)
@@ -282,7 +282,7 @@ class ModelFormBaseTest(TestCase):
"""
pass
- self.assertEqual(SubCategoryForm.base_fields.keys(),
+ self.assertEqual(list(SubCategoryForm.base_fields),
['name', 'slug', 'url'])
def test_subclassmeta_form(self):
@@ -312,7 +312,7 @@ class ModelFormBaseTest(TestCase):
model = Category
fields = ['url', 'name']
- self.assertEqual(OrderFields.base_fields.keys(),
+ self.assertEqual(list(OrderFields.base_fields),
['url', 'name'])
self.assertHTMLEqual(
str(OrderFields()),
@@ -327,7 +327,7 @@ class ModelFormBaseTest(TestCase):
fields = ['slug', 'url', 'name']
exclude = ['url']
- self.assertEqual(OrderFields2.base_fields.keys(),
+ self.assertEqual(list(OrderFields2.base_fields),
['slug', 'name'])
@@ -751,9 +751,9 @@ class OldFormForXTests(TestCase):
self.assertEqual(new_art.headline, 'New headline')
# Add some categories and test the many-to-many form output.
- self.assertEqual(map(lambda o: o.name, new_art.categories.all()), [])
+ self.assertQuerysetEqual(new_art.categories.all(), [])
new_art.categories.add(Category.objects.get(name='Entertainment'))
- self.assertEqual(map(lambda o: o.name, new_art.categories.all()), ["Entertainment"])
+ self.assertQuerysetEqual(new_art.categories.all(), ["Entertainment"])
f = TestArticleForm(auto_id=False, instance=new_art)
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
@@ -815,7 +815,7 @@ class OldFormForXTests(TestCase):
new_art = f.save()
self.assertEqual(new_art.id == art_id_1, True)
new_art = Article.objects.get(id=art_id_1)
- self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')),
+ self.assertQuerysetEqual(new_art.categories.order_by('name'),
["Entertainment", "It's a test"])
# Now, submit form data with no categories. This deletes the existing categories.
@@ -824,7 +824,7 @@ class OldFormForXTests(TestCase):
new_art = f.save()
self.assertEqual(new_art.id == art_id_1, True)
new_art = Article.objects.get(id=art_id_1)
- self.assertEqual(map(lambda o: o.name, new_art.categories.all()), [])
+ self.assertQuerysetEqual(new_art.categories.all(), [])
# Create a new article, with categories, via the form.
f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01',
@@ -833,7 +833,7 @@ class OldFormForXTests(TestCase):
art_id_2 = new_art.id
self.assertEqual(art_id_2 not in (None, art_id_1), True)
new_art = Article.objects.get(id=art_id_2)
- self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"])
# Create a new article, with no categories, via the form.
f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01',
@@ -842,7 +842,7 @@ class OldFormForXTests(TestCase):
art_id_3 = new_art.id
self.assertEqual(art_id_3 not in (None, art_id_1, art_id_2), True)
new_art = Article.objects.get(id=art_id_3)
- self.assertEqual(map(lambda o: o.name, new_art.categories.all()), [])
+ self.assertQuerysetEqual(new_art.categories.all(), [])
# Create a new article, with categories, via the form, but use commit=False.
# The m2m data won't be saved until save_m2m() is invoked on the form.
@@ -857,11 +857,11 @@ class OldFormForXTests(TestCase):
# The instance doesn't have m2m data yet
new_art = Article.objects.get(id=art_id_4)
- self.assertEqual(map(lambda o: o.name, new_art.categories.all()), [])
+ self.assertQuerysetEqual(new_art.categories.all(), [])
# Save the m2m data on the form
f.save_m2m()
- self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"])
# Here, we define a custom ModelForm. Because it happens to have the same fields as
# the Category model, we can just call the form's save() to apply its changes to an
@@ -1007,12 +1007,12 @@ class OldFormForXTests(TestCase):
f.clean(None)
with self.assertRaises(ValidationError):
f.clean([])
- self.assertEqual(map(lambda o: o.name, f.clean([c1.id])), ["Entertainment"])
- self.assertEqual(map(lambda o: o.name, f.clean([c2.id])), ["It's a test"])
- self.assertEqual(map(lambda o: o.name, f.clean([str(c1.id)])), ["Entertainment"])
- self.assertEqual(map(lambda o: o.name, f.clean([str(c1.id), str(c2.id)])), ["Entertainment", "It's a test"])
- self.assertEqual(map(lambda o: o.name, f.clean([c1.id, str(c2.id)])), ["Entertainment", "It's a test"])
- self.assertEqual(map(lambda o: o.name, f.clean((c1.id, str(c2.id)))), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(f.clean([c1.id]), ["Entertainment"])
+ self.assertQuerysetEqual(f.clean([c2.id]), ["It's a test"])
+ self.assertQuerysetEqual(f.clean([str(c1.id)]), ["Entertainment"])
+ self.assertQuerysetEqual(f.clean([str(c1.id), str(c2.id)]), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"])
with self.assertRaises(ValidationError):
f.clean(['100'])
with self.assertRaises(ValidationError):
@@ -1025,7 +1025,7 @@ class OldFormForXTests(TestCase):
# than caching it at time of instantiation.
c6 = Category.objects.create(id=6, name='Sixth', url='6th')
self.assertEqual(c6.name, 'Sixth')
- self.assertEqual(map(lambda o: o.name, f.clean([c6.id])), ["Sixth"])
+ self.assertQuerysetEqual(f.clean([c6.id]), ["Sixth"])
# Delete a Category object *after* the ModelMultipleChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
@@ -1050,7 +1050,7 @@ class OldFormForXTests(TestCase):
(c1.pk, 'Entertainment'),
(c2.pk, "It's a test"),
(c3.pk, 'Third')])
- self.assertEqual(map(lambda o: o.name, f.clean([c3.id])), ["Third"])
+ self.assertQuerysetEqual(f.clean([c3.id]), ["Third"])
with self.assertRaises(ValidationError):
f.clean([c4.id])
with self.assertRaises(ValidationError):
@@ -1066,13 +1066,13 @@ class OldFormForXTests(TestCase):
# OneToOneField ###############################################################
- self.assertEqual(ImprovedArticleForm.base_fields.keys(), ['article'])
+ self.assertEqual(list(ImprovedArticleForm.base_fields), ['article'])
- self.assertEqual(ImprovedArticleWithParentLinkForm.base_fields.keys(), [])
+ self.assertEqual(list(ImprovedArticleWithParentLinkForm.base_fields), [])
bw = BetterWriter(name='Joe Better', score=10)
bw.save()
- self.assertEqual(sorted(model_to_dict(bw).keys()),
+ self.assertEqual(sorted(model_to_dict(bw)),
['id', 'name', 'score', 'writer_ptr'])
form = BetterWriterForm({'name': 'Some Name', 'score': 12})
@@ -1463,7 +1463,7 @@ class OldFormForXTests(TestCase):
model = Category
fields = ['description', 'url']
- self.assertEqual(CategoryForm.base_fields.keys(),
+ self.assertEqual(list(CategoryForm.base_fields),
['description', 'url'])
self.assertHTMLEqual(six.text_type(CategoryForm()), '''<tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr>
@@ -1472,14 +1472,15 @@ class OldFormForXTests(TestCase):
field = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode')
self.assertEqual(tuple(field.choices), ((86, 'Apple'), (87, 'Core'), (22, 'Pear')))
- self.assertEqual(map(lambda o: o.name, field.clean([86])), ['Apple'])
+ self.assertQuerysetEqual(field.clean([86]), ['Apple'])
form = SelectInventoryForm({'items': [87, 22]})
self.assertEqual(form.is_valid(), True)
self.assertEqual(len(form.cleaned_data), 1)
- self.assertEqual(map(lambda o: o.name, form.cleaned_data['items']), ['Core', 'Pear'])
+ self.assertQuerysetEqual(form.cleaned_data['items'], ['Core', 'Pear'])
def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self):
- self.assertEqual(CustomFieldForExclusionForm.base_fields.keys(), ['name'])
+ self.assertEqual(list(CustomFieldForExclusionForm.base_fields),
+ ['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>''')
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
index a01a2da0fa..ae152448ab 100644
--- a/tests/modeltests/model_formsets/models.py
+++ b/tests/modeltests/model_formsets/models.py
@@ -4,20 +4,23 @@ import datetime
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
class BetterAuthor(Author):
write_speed = models.IntegerField()
+@python_2_unicode_compatible
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
@@ -28,20 +31,22 @@ class Book(models.Model):
)
ordering = ['id']
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class BookWithCustomPK(models.Model):
my_pk = models.DecimalField(max_digits=5, decimal_places=0, primary_key=True)
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return '%s: %s' % (self.my_pk, self.title)
class Editor(models.Model):
name = models.CharField(max_length=100)
+@python_2_unicode_compatible
class BookWithOptionalAltEditor(models.Model):
author = models.ForeignKey(Author)
# Optional secondary author
@@ -53,21 +58,23 @@ class BookWithOptionalAltEditor(models.Model):
('author', 'title', 'alt_editor'),
)
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class AlternateBook(Book):
notes = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return '%s - %s' % (self.title, self.notes)
+@python_2_unicode_compatible
class AuthorMeeting(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
created = models.DateField(editable=False)
- def __unicode__(self):
+ def __str__(self):
return self.name
class CustomPrimaryKey(models.Model):
@@ -77,19 +84,21 @@ class CustomPrimaryKey(models.Model):
# models for inheritance tests.
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=50)
city = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Owner(models.Model):
auto_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
place = models.ForeignKey(Place)
- def __unicode__(self):
+ def __str__(self):
return "%s at %s" % (self.name, self.place)
class Location(models.Model):
@@ -98,30 +107,34 @@ class Location(models.Model):
lat = models.CharField(max_length=100)
lon = models.CharField(max_length=100)
+@python_2_unicode_compatible
class OwnerProfile(models.Model):
owner = models.OneToOneField(Owner, primary_key=True)
age = models.PositiveIntegerField()
- def __unicode__(self):
+ def __str__(self):
return "%s is %d" % (self.owner.name, self.age)
+@python_2_unicode_compatible
class Restaurant(Place):
serves_pizza = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Product(models.Model):
slug = models.SlugField(unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.slug
+@python_2_unicode_compatible
class Price(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveIntegerField()
- def __unicode__(self):
+ def __str__(self):
return "%s for %s" % (self.quantity, self.price)
class Meta:
@@ -136,12 +149,14 @@ class ClassyMexicanRestaurant(MexicanRestaurant):
# models for testing unique_together validation when a fk is involved and
# using inlineformset_factory.
+@python_2_unicode_compatible
class Repository(models.Model):
name = models.CharField(max_length=25)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Revision(models.Model):
repository = models.ForeignKey(Repository)
revision = models.CharField(max_length=40)
@@ -149,7 +164,7 @@ class Revision(models.Model):
class Meta:
unique_together = (("repository", "revision"),)
- def __unicode__(self):
+ def __str__(self):
return "%s (%s)" % (self.revision, six.text_type(self.repository))
# models for testing callable defaults (see bug #7975). If you define a model
@@ -167,32 +182,36 @@ class Membership(models.Model):
class Team(models.Model):
name = models.CharField(max_length=100)
+@python_2_unicode_compatible
class Player(models.Model):
team = models.ForeignKey(Team, null=True)
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
# Models for testing custom ModelForm save methods in formsets and inline formsets
+@python_2_unicode_compatible
class Poet(models.Model):
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Poem(models.Model):
poet = models.ForeignKey(Poet)
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Post(models.Model):
title = models.CharField(max_length=50, unique_for_date='posted', blank=True)
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()
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index 37ca603021..2101f394f7 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -14,11 +14,13 @@ Both styles are demonstrated here.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
#
# Abstract base classes
#
+@python_2_unicode_compatible
class CommonInfo(models.Model):
name = models.CharField(max_length=50)
age = models.PositiveIntegerField()
@@ -27,7 +29,7 @@ class CommonInfo(models.Model):
abstract = True
ordering = ['name']
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.__class__.__name__, self.name)
class Worker(CommonInfo):
@@ -49,6 +51,7 @@ class StudentWorker(Student, Worker):
class Post(models.Model):
title = models.CharField(max_length=50)
+@python_2_unicode_compatible
class Attachment(models.Model):
post = models.ForeignKey(Post, related_name='attached_%(class)s_set')
content = models.TextField()
@@ -56,7 +59,7 @@ class Attachment(models.Model):
class Meta:
abstract = True
- def __unicode__(self):
+ def __str__(self):
return self.content
class Comment(Attachment):
@@ -69,17 +72,19 @@ class Link(Attachment):
# Multi-table inheritance
#
+@python_2_unicode_compatible
class Chef(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return "%s the chef" % self.name
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
- def __unicode__(self):
+ def __str__(self):
return "%s the place" % self.name
class Rating(models.Model):
@@ -89,6 +94,7 @@ class Rating(models.Model):
abstract = True
ordering = ['-rating']
+@python_2_unicode_compatible
class Restaurant(Place, Rating):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
@@ -97,27 +103,30 @@ class Restaurant(Place, Rating):
class Meta(Rating.Meta):
db_table = 'my_restaurant'
- def __unicode__(self):
+ def __str__(self):
return "%s the restaurant" % self.name
+@python_2_unicode_compatible
class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the italian restaurant" % self.name
+@python_2_unicode_compatible
class Supplier(Place):
customers = models.ManyToManyField(Restaurant, related_name='provider')
- def __unicode__(self):
+ def __str__(self):
return "%s the supplier" % self.name
+@python_2_unicode_compatible
class ParkingLot(Place):
# An explicit link to the parent (we can control the attribute name).
parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
main_site = models.ForeignKey(Place, related_name='lot')
- def __unicode__(self):
+ def __str__(self):
return "%s the parking lot" % self.name
#
@@ -139,10 +148,11 @@ class NamedURL(models.Model):
class Meta:
abstract = True
+@python_2_unicode_compatible
class Copy(NamedURL):
content = models.TextField()
- def __unicode__(self):
+ def __str__(self):
return self.content
class Mixin(object):
diff --git a/tests/modeltests/model_inheritance_same_model_name/models.py b/tests/modeltests/model_inheritance_same_model_name/models.py
index de7541694a..801724df18 100644
--- a/tests/modeltests/model_inheritance_same_model_name/models.py
+++ b/tests/modeltests/model_inheritance_same_model_name/models.py
@@ -11,12 +11,14 @@ from __future__ import absolute_import
from django.db import models
from ..model_inheritance.models import NamedURL
+from django.utils.encoding import python_2_unicode_compatible
#
# Abstract base classes with related models
#
+@python_2_unicode_compatible
class Copy(NamedURL):
content = models.TextField()
- def __unicode__(self):
+ def __str__(self):
return self.content
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
index 8c6d56a795..9599496cb7 100644
--- a/tests/modeltests/one_to_one/models.py
+++ b/tests/modeltests/one_to_one/models.py
@@ -8,28 +8,32 @@ In this example, a ``Place`` optionally can be a ``Restaurant``.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
- def __unicode__(self):
+ def __str__(self):
return "%s the place" % self.name
+@python_2_unicode_compatible
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the restaurant" % self.place.name
+@python_2_unicode_compatible
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant)
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)
class ManualPrimaryKey(models.Model):
@@ -40,10 +44,11 @@ class RelatedModel(models.Model):
link = models.OneToOneField(ManualPrimaryKey)
name = models.CharField(max_length = 50)
+@python_2_unicode_compatible
class MultiModel(models.Model):
link1 = models.OneToOneField(Place)
link2 = models.OneToOneField(ManualPrimaryKey)
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return "Multimodel %s" % self.name
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
index 0037b41afb..f146b2e72d 100644
--- a/tests/modeltests/or_lookups/models.py
+++ b/tests/modeltests/or_lookups/models.py
@@ -10,8 +10,10 @@ clauses using the variable ``django.db.models.Q`` (or any object with an
"""
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=50)
pub_date = models.DateTimeField()
@@ -19,5 +21,5 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date',)
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/order_with_respect_to/models.py b/tests/modeltests/order_with_respect_to/models.py
index a4e20c2fe0..06bb56b141 100644
--- a/tests/modeltests/order_with_respect_to/models.py
+++ b/tests/modeltests/order_with_respect_to/models.py
@@ -4,11 +4,13 @@ Tests for the order_with_respect_to Meta attribute.
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class Question(models.Model):
text = models.CharField(max_length=200)
+@python_2_unicode_compatible
class Answer(models.Model):
text = models.CharField(max_length=200)
question = models.ForeignKey(Question)
@@ -16,9 +18,10 @@ class Answer(models.Model):
class Meta:
order_with_respect_to = 'question'
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.text)
+@python_2_unicode_compatible
class Post(models.Model):
title = models.CharField(max_length=200)
parent = models.ForeignKey("self", related_name="children", null=True)
@@ -26,5 +29,5 @@ class Post(models.Model):
class Meta:
order_with_respect_to = "parent"
- def __unicode__(self):
+ def __str__(self):
return self.title
diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py
index bfb4b97107..67126e1bda 100644
--- a/tests/modeltests/ordering/models.py
+++ b/tests/modeltests/ordering/models.py
@@ -14,22 +14,25 @@ undefined -- not random, just undefined.
"""
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.DateTimeField()
class Meta:
ordering = ('-pub_date', 'headline')
- def __unicode__(self):
+ def __str__(self):
return self.headline
+@python_2_unicode_compatible
class ArticlePKOrdering(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
class Meta:
ordering = ('-pk',)
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 48484dd59b..779d3029ba 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -7,11 +7,13 @@ objects into easily readable pages.
"""
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, default='Default headline')
pub_date = models.DateTimeField()
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/modeltests/pagination/tests.py b/tests/modeltests/pagination/tests.py
index cba8825ec4..12ce0e3ecb 100644
--- a/tests/modeltests/pagination/tests.py
+++ b/tests/modeltests/pagination/tests.py
@@ -28,7 +28,7 @@ class PaginationTests(TestCase):
paginator = Paginator(Article.objects.all(), 5)
self.assertEqual(9, paginator.count)
self.assertEqual(2, paginator.num_pages)
- self.assertEqual([1, 2], paginator.page_range)
+ self.assertEqual([1, 2], list(paginator.page_range))
def test_first_page(self):
paginator = Paginator(Article.objects.all(), 5)
@@ -78,13 +78,13 @@ class PaginationTests(TestCase):
paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True)
self.assertEqual(0, paginator.count)
self.assertEqual(1, paginator.num_pages)
- self.assertEqual([1], paginator.page_range)
+ self.assertEqual([1], list(paginator.page_range))
# Empty paginators with allow_empty_first_page=False.
paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False)
self.assertEqual(0, paginator.count)
self.assertEqual(0, paginator.num_pages)
- self.assertEqual([], paginator.page_range)
+ self.assertEqual([], list(paginator.page_range))
def test_invalid_page(self):
paginator = Paginator(Article.objects.all(), 5)
@@ -108,7 +108,7 @@ class PaginationTests(TestCase):
paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
self.assertEqual(9, paginator.count)
self.assertEqual(3, paginator.num_pages)
- self.assertEqual([1, 2, 3], paginator.page_range)
+ self.assertEqual([1, 2, 3], list(paginator.page_range))
p = paginator.page(2)
self.assertEqual("<Page 2 of 3>", six.text_type(p))
self.assertEqual([4, 5, 6], p.object_list)
@@ -125,10 +125,10 @@ class PaginationTests(TestCase):
paginator = Paginator(CountContainer(), 10)
self.assertEqual(42, paginator.count)
self.assertEqual(5, paginator.num_pages)
- self.assertEqual([1, 2, 3, 4, 5], paginator.page_range)
+ self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
# Paginator can be passed other objects that implement __len__.
paginator = Paginator(LenContainer(), 10)
self.assertEqual(42, paginator.count)
self.assertEqual(5, paginator.num_pages)
- self.assertEqual([1, 2, 3, 4, 5], paginator.page_range)
+ self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
diff --git a/tests/modeltests/prefetch_related/models.py b/tests/modeltests/prefetch_related/models.py
index 589f78c7d3..85488f0879 100644
--- a/tests/modeltests/prefetch_related/models.py
+++ b/tests/modeltests/prefetch_related/models.py
@@ -1,16 +1,18 @@
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
## Basic tests
+@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=50, unique=True)
first_book = models.ForeignKey('Book', related_name='first_time_authors')
favorite_authors = models.ManyToManyField(
'self', through='FavoriteAuthors', symmetrical=False, related_name='favors_me')
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
@@ -30,6 +32,7 @@ class FavoriteAuthors(models.Model):
ordering = ['id']
+@python_2_unicode_compatible
class AuthorAddress(models.Model):
author = models.ForeignKey(Author, to_field='name', related_name='addresses')
address = models.TextField()
@@ -37,15 +40,16 @@ class AuthorAddress(models.Model):
class Meta:
ordering = ['id']
- def __unicode__(self):
+ def __str__(self):
return self.address
+@python_2_unicode_compatible
class Book(models.Model):
title = models.CharField(max_length=255)
authors = models.ManyToManyField(Author, related_name='books')
- def __unicode__(self):
+ def __str__(self):
return self.title
class Meta:
@@ -58,11 +62,12 @@ class BookWithYear(Book):
AuthorWithAge, related_name='books_with_year')
+@python_2_unicode_compatible
class Reader(models.Model):
name = models.CharField(max_length=50)
books_read = models.ManyToManyField(Book, related_name='read_by')
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
@@ -86,13 +91,14 @@ class TeacherManager(models.Manager):
return super(TeacherManager, self).get_query_set().prefetch_related('qualifications')
+@python_2_unicode_compatible
class Teacher(models.Model):
name = models.CharField(max_length=50)
qualifications = models.ManyToManyField(Qualification)
objects = TeacherManager()
- def __unicode__(self):
+ def __str__(self):
return "%s (%s)" % (self.name, ", ".join(q.name for q in self.qualifications.all()))
class Meta:
@@ -109,6 +115,7 @@ class Department(models.Model):
## GenericRelation/GenericForeignKey tests
+@python_2_unicode_compatible
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, related_name="taggeditem_set2")
@@ -119,7 +126,7 @@ class TaggedItem(models.Model):
created_by_fkey = models.PositiveIntegerField(null=True)
created_by = generic.GenericForeignKey('created_by_ct', 'created_by_fkey',)
- def __unicode__(self):
+ def __str__(self):
return self.tag
@@ -169,12 +176,13 @@ class Person(models.Model):
## Models for nullable FK tests
+@python_2_unicode_compatible
class Employee(models.Model):
name = models.CharField(max_length=50)
boss = models.ForeignKey('self', null=True,
related_name='serfs')
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py
index 8ae0a1e298..614a5fc1d6 100644
--- a/tests/modeltests/prefetch_related/tests.py
+++ b/tests/modeltests/prefetch_related/tests.py
@@ -352,7 +352,7 @@ class MultiTableInheritanceTest(TestCase):
with self.assertNumQueries(2):
qs = BookReview.objects.prefetch_related('book')
titles = [obj.book.title for obj in qs]
- self.assertEquals(titles, ["Poems", "More poems"])
+ self.assertEqual(titles, ["Poems", "More poems"])
def test_m2m_to_inheriting_model(self):
qs = AuthorWithAge.objects.prefetch_related('books_with_year')
diff --git a/tests/modeltests/proxy_model_inheritance/tests.py b/tests/modeltests/proxy_model_inheritance/tests.py
index 061cdc7e91..39fee7ee6d 100644
--- a/tests/modeltests/proxy_model_inheritance/tests.py
+++ b/tests/modeltests/proxy_model_inheritance/tests.py
@@ -24,7 +24,8 @@ class ProxyModelInheritanceTests(TransactionTestCase):
def setUp(self):
self.old_sys_path = sys.path[:]
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
- map(load_app, settings.INSTALLED_APPS)
+ for app in settings.INSTALLED_APPS:
+ load_app(app)
def tearDown(self):
sys.path = self.old_sys_path
diff --git a/tests/modeltests/proxy_models/models.py b/tests/modeltests/proxy_models/models.py
index 49fd87deff..6c962aadc8 100644
--- a/tests/modeltests/proxy_models/models.py
+++ b/tests/modeltests/proxy_models/models.py
@@ -5,6 +5,7 @@ than using a new table of their own. This allows them to act as simple proxies,
providing a modified interface to the data from the base class.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# A couple of managers for testing managing overriding in proxy model cases.
@@ -16,6 +17,7 @@ class SubManager(models.Manager):
def get_query_set(self):
return super(SubManager, self).get_query_set().exclude(name="wilma")
+@python_2_unicode_compatible
class Person(models.Model):
"""
A simple concrete base class.
@@ -24,7 +26,7 @@ class Person(models.Model):
objects = PersonManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
class Abstract(models.Model):
@@ -82,10 +84,11 @@ class MyPersonProxy(MyPerson):
class LowerStatusPerson(MyPersonProxy):
status = models.CharField(max_length=80)
+@python_2_unicode_compatible
class User(models.Model):
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
class UserProxy(User):
@@ -100,11 +103,12 @@ class UserProxyProxy(UserProxy):
class Country(models.Model):
name = models.CharField(max_length=50)
+@python_2_unicode_compatible
class State(models.Model):
name = models.CharField(max_length=50)
country = models.ForeignKey(Country)
- def __unicode__(self):
+ def __str__(self):
return self.name
class StateProxy(State):
@@ -124,11 +128,12 @@ class ProxyTrackerUser(TrackerUser):
proxy = True
+@python_2_unicode_compatible
class Issue(models.Model):
summary = models.CharField(max_length=255)
assignee = models.ForeignKey(TrackerUser)
- def __unicode__(self):
+ def __str__(self):
return ':'.join((self.__class__.__name__,self.summary,))
class Bug(Issue):
diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py
index 010649e681..8e942b20e8 100644
--- a/tests/modeltests/reserved_names/models.py
+++ b/tests/modeltests/reserved_names/models.py
@@ -8,8 +8,10 @@ reserved-name usage.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Thing(models.Model):
when = models.CharField(max_length=1, primary_key=True)
join = models.CharField(max_length=1)
@@ -22,5 +24,5 @@ class Thing(models.Model):
class Meta:
db_table = 'select'
- def __unicode__(self):
- return self.when \ No newline at end of file
+ def __str__(self):
+ return self.when
diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py
index bb7a163327..ed58177770 100644
--- a/tests/modeltests/reverse_lookup/models.py
+++ b/tests/modeltests/reverse_lookup/models.py
@@ -5,25 +5,29 @@ This demonstrates the reverse lookup features of the database API.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class User(models.Model):
name = models.CharField(max_length=200)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Poll(models.Model):
question = models.CharField(max_length=200)
creator = models.ForeignKey(User)
- def __unicode__(self):
+ def __str__(self):
return self.question
+@python_2_unicode_compatible
class Choice(models.Model):
name = models.CharField(max_length=100)
poll = models.ForeignKey(Poll, related_name="poll_choice")
related_poll = models.ForeignKey(Poll, related_name="related_choice")
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py
index bae60e4b86..a6e1abfb77 100644
--- a/tests/modeltests/save_delete_hooks/models.py
+++ b/tests/modeltests/save_delete_hooks/models.py
@@ -7,8 +7,10 @@ the methods.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Person(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
@@ -17,7 +19,7 @@ class Person(models.Model):
super(Person, self).__init__(*args, **kwargs)
self.data = []
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
def save(self, *args, **kwargs):
diff --git a/tests/modeltests/select_related/models.py b/tests/modeltests/select_related/models.py
index 3c2e7721fd..ec41957adf 100644
--- a/tests/modeltests/select_related/models.py
+++ b/tests/modeltests/select_related/models.py
@@ -8,52 +8,61 @@ the select-related behavior will traverse.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# Who remembers high school biology?
+@python_2_unicode_compatible
class Domain(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Kingdom(models.Model):
name = models.CharField(max_length=50)
domain = models.ForeignKey(Domain)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Phylum(models.Model):
name = models.CharField(max_length=50)
kingdom = models.ForeignKey(Kingdom)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Klass(models.Model):
name = models.CharField(max_length=50)
phylum = models.ForeignKey(Phylum)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Order(models.Model):
name = models.CharField(max_length=50)
klass = models.ForeignKey(Klass)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Family(models.Model):
name = models.CharField(max_length=50)
order = models.ForeignKey(Order)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Genus(models.Model):
name = models.CharField(max_length=50)
family = models.ForeignKey(Family)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Species(models.Model):
name = models.CharField(max_length=50)
genus = models.ForeignKey(Genus)
- def __unicode__(self):
- return self.name \ No newline at end of file
+ def __str__(self):
+ return self.name
diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py
index 9da099c027..569f2cec49 100644
--- a/tests/modeltests/serializers/models.py
+++ b/tests/modeltests/serializers/models.py
@@ -11,28 +11,32 @@ from decimal import Decimal
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=20)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Article(models.Model):
author = models.ForeignKey(Author)
headline = models.CharField(max_length=50)
@@ -42,28 +46,31 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date',)
- def __unicode__(self):
+ def __str__(self):
return self.headline
+@python_2_unicode_compatible
class AuthorProfile(models.Model):
author = models.OneToOneField(Author, primary_key=True)
date_of_birth = models.DateField()
- def __unicode__(self):
+ def __str__(self):
return "Profile of %s" % self.author
+@python_2_unicode_compatible
class Actor(models.Model):
name = models.CharField(max_length=20, primary_key=True)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Movie(models.Model):
actor = models.ForeignKey(Actor)
title = models.CharField(max_length=50)
@@ -72,7 +79,7 @@ class Movie(models.Model):
class Meta:
ordering = ('title',)
- def __unicode__(self):
+ def __str__(self):
return self.title
@@ -80,13 +87,11 @@ class Score(models.Model):
score = models.FloatField()
+@python_2_unicode_compatible
class Team(object):
def __init__(self, title):
self.title = title
- def __unicode__(self):
- raise NotImplementedError("Not so simple")
-
def __str__(self):
raise NotImplementedError("Not so simple")
@@ -94,8 +99,7 @@ class Team(object):
return "%s" % self.title
-class TeamField(models.CharField):
- __metaclass__ = models.SubfieldBase
+class TeamField(six.with_metaclass(models.SubfieldBase, models.CharField)):
def __init__(self):
super(TeamField, self).__init__(max_length=100)
@@ -112,10 +116,11 @@ class TeamField(models.CharField):
return self._get_val_from_obj(obj).to_string()
+@python_2_unicode_compatible
class Player(models.Model):
name = models.CharField(max_length=50)
rank = models.IntegerField()
team = TeamField()
- def __unicode__(self):
+ def __str__(self):
return '%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string())
diff --git a/tests/modeltests/serializers/tests.py b/tests/modeltests/serializers/tests.py
index ec3cc132db..e39f17b68a 100644
--- a/tests/modeltests/serializers/tests.py
+++ b/tests/modeltests/serializers/tests.py
@@ -114,8 +114,8 @@ class SerializersTestBase(object):
Tests the ability to create new objects by
modifying serialized content.
"""
- old_headline = b"Poker has no place on ESPN"
- new_headline = b"Poker has no place on television"
+ old_headline = "Poker has no place on ESPN"
+ new_headline = "Poker has no place on television"
serial_str = serializers.serialize(self.serializer_name,
Article.objects.all())
serial_str = serial_str.replace(old_headline, new_headline)
@@ -285,7 +285,7 @@ class SerializersTransactionTestBase(object):
class XmlSerializerTestCase(SerializersTestBase, TestCase):
serializer_name = "xml"
- pkless_str = b"""<?xml version="1.0" encoding="utf-8"?>
+ pkless_str = """<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object model="serializers.category">
<field type="CharField" name="name">Reference</field>
@@ -331,7 +331,7 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase):
class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase):
serializer_name = "xml"
- fwd_ref_str = b"""<?xml version="1.0" encoding="utf-8"?>
+ fwd_ref_str = """<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="1" model="serializers.article">
<field to="serializers.author" name="author" rel="ManyToOneRel">1</field>
@@ -351,7 +351,7 @@ class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, Transacti
class JsonSerializerTestCase(SerializersTestBase, TestCase):
serializer_name = "json"
- pkless_str = b"""[{"pk": null, "model": "serializers.category", "fields": {"name": "Reference"}}]"""
+ pkless_str = """[{"pk": null, "model": "serializers.category", "fields": {"name": "Reference"}}]"""
@staticmethod
def _validate_output(serial_str):
@@ -381,7 +381,7 @@ class JsonSerializerTestCase(SerializersTestBase, TestCase):
class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase):
serializer_name = "json"
- fwd_ref_str = b"""[
+ fwd_ref_str = """[
{
"pk": 1,
"model": "serializers.article",
@@ -414,7 +414,7 @@ except ImportError:
else:
class YamlSerializerTestCase(SerializersTestBase, TestCase):
serializer_name = "yaml"
- fwd_ref_str = b"""- fields:
+ fwd_ref_str = """- fields:
headline: Forward references pose no problem
pub_date: 2006-06-16 15:00:00
categories: [1]
@@ -430,7 +430,7 @@ else:
pk: 1
model: serializers.author"""
- pkless_str = b"""- fields:
+ pkless_str = """- fields:
name: Reference
pk: null
model: serializers.category"""
@@ -470,7 +470,7 @@ else:
class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase):
serializer_name = "yaml"
- fwd_ref_str = b"""- fields:
+ fwd_ref_str = """- fields:
headline: Forward references pose no problem
pub_date: 2006-06-16 15:00:00
categories: [1]
diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py
index 18ca467655..e54a22fce9 100644
--- a/tests/modeltests/signals/models.py
+++ b/tests/modeltests/signals/models.py
@@ -4,18 +4,21 @@ Testing signals before/after saving and deleting.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Person(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Car(models.Model):
make = models.CharField(max_length=20)
model = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.make, self.model)
diff --git a/tests/modeltests/str/models.py b/tests/modeltests/str/models.py
index 6f6b877c88..488012e861 100644
--- a/tests/modeltests/str/models.py
+++ b/tests/modeltests/str/models.py
@@ -15,6 +15,7 @@ if you prefer. You must be careful to encode the results correctly, though.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class Article(models.Model):
@@ -26,9 +27,10 @@ class Article(models.Model):
# in ASCII.
return self.headline
+@python_2_unicode_compatible
class InternationalArticle(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
- def __unicode__(self):
- return self.headline \ No newline at end of file
+ def __str__(self):
+ return self.headline
diff --git a/tests/modeltests/str/tests.py b/tests/modeltests/str/tests.py
index bed9ea719b..2c11ac8c78 100644
--- a/tests/modeltests/str/tests.py
+++ b/tests/modeltests/str/tests.py
@@ -4,23 +4,32 @@ from __future__ import absolute_import, unicode_literals
import datetime
from django.test import TestCase
+from django.utils import six
+from django.utils.unittest import skipIf
from .models import Article, InternationalArticle
class SimpleTests(TestCase):
+
+ @skipIf(six.PY3, "tests a __str__ method returning unicode under Python 2")
def test_basic(self):
a = Article.objects.create(
headline=b'Area man programs in Python',
pub_date=datetime.datetime(2005, 7, 28)
)
- self.assertEqual(str(a), b'Area man programs in Python')
- self.assertEqual(repr(a), b'<Article: Area man programs in Python>')
+ self.assertEqual(str(a), str('Area man programs in Python'))
+ self.assertEqual(repr(a), str('<Article: Area man programs in Python>'))
def test_international(self):
a = InternationalArticle.objects.create(
headline='Girl wins €12.500 in lottery',
pub_date=datetime.datetime(2005, 7, 28)
)
- # The default str() output will be the UTF-8 encoded output of __unicode__().
- self.assertEqual(str(a), b'Girl wins \xe2\x82\xac12.500 in lottery')
+ if six.PY3:
+ self.assertEqual(str(a), 'Girl wins €12.500 in lottery')
+ else:
+ # On Python 2, the default str() output will be the UTF-8 encoded
+ # output of __unicode__() -- or __str__() when the
+ # python_2_unicode_compatible decorator is used.
+ self.assertEqual(str(a), b'Girl wins \xe2\x82\xac12.500 in lottery')
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index 399d2906a8..1d9c999f21 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -91,7 +91,7 @@ class ClientTest(TestCase):
content_type="text/xml")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.templates[0].name, "Book template")
- self.assertEqual(response.content, "Blink - Malcolm Gladwell")
+ self.assertEqual(response.content, b"Blink - Malcolm Gladwell")
def test_redirect(self):
"GET a URL that redirects elsewhere"
diff --git a/tests/modeltests/timezones/tests.py b/tests/modeltests/timezones/tests.py
index a38e4b3f75..4621f0a60c 100644
--- a/tests/modeltests/timezones/tests.py
+++ b/tests/modeltests/timezones/tests.py
@@ -467,6 +467,7 @@ class NewDatabaseTests(TestCase):
[event],
transform=lambda d: d)
+ @requires_tz_support
def test_filter_date_field_with_aware_datetime(self):
# Regression test for #17742
day = datetime.date(2011, 9, 1)
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
index 7a51b9ac1c..0f8d6b16ec 100644
--- a/tests/modeltests/transactions/models.py
+++ b/tests/modeltests/transactions/models.py
@@ -9,8 +9,10 @@ manually.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@@ -19,5 +21,5 @@ class Reporter(models.Model):
class Meta:
ordering = ('first_name', 'last_name')
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
diff --git a/tests/modeltests/unmanaged_models/models.py b/tests/modeltests/unmanaged_models/models.py
index 00303cf17a..0eef69977c 100644
--- a/tests/modeltests/unmanaged_models/models.py
+++ b/tests/modeltests/unmanaged_models/models.py
@@ -4,9 +4,11 @@ is generated for the table on various manage.py operations.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# All of these models are created in the database by Django.
+@python_2_unicode_compatible
class A01(models.Model):
f_a = models.CharField(max_length=10, db_index=True)
f_b = models.IntegerField()
@@ -14,9 +16,10 @@ class A01(models.Model):
class Meta:
db_table = 'a01'
- def __unicode__(self):
+ def __str__(self):
return self.f_a
+@python_2_unicode_compatible
class B01(models.Model):
fk_a = models.ForeignKey(A01)
f_a = models.CharField(max_length=10, db_index=True)
@@ -27,9 +30,10 @@ class B01(models.Model):
# 'managed' is True by default. This tests we can set it explicitly.
managed = True
- def __unicode__(self):
+ def __str__(self):
return self.f_a
+@python_2_unicode_compatible
class C01(models.Model):
mm_a = models.ManyToManyField(A01, db_table='d01')
f_a = models.CharField(max_length=10, db_index=True)
@@ -38,13 +42,14 @@ class C01(models.Model):
class Meta:
db_table = 'c01'
- def __unicode__(self):
+ def __str__(self):
return self.f_a
# All of these models use the same tables as the previous set (they are shadows
# of possibly a subset of the columns). There should be no creation errors,
# since we have told Django they aren't managed by Django.
+@python_2_unicode_compatible
class A02(models.Model):
f_a = models.CharField(max_length=10, db_index=True)
@@ -52,9 +57,10 @@ class A02(models.Model):
db_table = 'a01'
managed = False
- def __unicode__(self):
+ def __str__(self):
return self.f_a
+@python_2_unicode_compatible
class B02(models.Model):
class Meta:
db_table = 'b01'
@@ -64,11 +70,12 @@ class B02(models.Model):
f_a = models.CharField(max_length=10, db_index=True)
f_b = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.f_a
# To re-use the many-to-many intermediate table, we need to manually set up
# things up.
+@python_2_unicode_compatible
class C02(models.Model):
mm_a = models.ManyToManyField(A02, through="Intermediate")
f_a = models.CharField(max_length=10, db_index=True)
@@ -78,7 +85,7 @@ class C02(models.Model):
db_table = 'c01'
managed = False
- def __unicode__(self):
+ def __str__(self):
return self.f_a
class Intermediate(models.Model):
diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py
index b93e4a7aae..08472d98b1 100644
--- a/tests/modeltests/update/models.py
+++ b/tests/modeltests/update/models.py
@@ -5,21 +5,24 @@ updates.
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class DataPoint(models.Model):
name = models.CharField(max_length=20)
value = models.CharField(max_length=20)
another_value = models.CharField(max_length=20, blank=True)
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.name)
+@python_2_unicode_compatible
class RelatedPoint(models.Model):
name = models.CharField(max_length=20)
data = models.ForeignKey(DataPoint)
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.name)
diff --git a/tests/modeltests/update_only_fields/models.py b/tests/modeltests/update_only_fields/models.py
index 968dba9916..bf5dd99166 100644
--- a/tests/modeltests/update_only_fields/models.py
+++ b/tests/modeltests/update_only_fields/models.py
@@ -1,5 +1,6 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
GENDER_CHOICES = (
('M', 'Male'),
@@ -10,11 +11,13 @@ class Account(models.Model):
num = models.IntegerField()
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=20)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
+ pid = models.IntegerField(null=True, default=None)
- def __unicode__(self):
+ def __str__(self):
return self.name
@@ -24,11 +27,12 @@ class Employee(Person):
accounts = models.ManyToManyField('Account', related_name='employees', blank=True, null=True)
+@python_2_unicode_compatible
class Profile(models.Model):
name = models.CharField(max_length=200)
salary = models.FloatField(default=1000.0)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/modeltests/update_only_fields/tests.py b/tests/modeltests/update_only_fields/tests.py
index bce53ca621..c904c97236 100644
--- a/tests/modeltests/update_only_fields/tests.py
+++ b/tests/modeltests/update_only_fields/tests.py
@@ -18,6 +18,107 @@ class UpdateOnlyFieldsTests(TestCase):
self.assertEqual(s.gender, 'F')
self.assertEqual(s.name, 'Ian')
+ def test_update_fields_deferred(self):
+ s = Person.objects.create(name='Sara', gender='F', pid=22)
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.defer("gender", "pid").get(pk=s.pk)
+ s1.name = "Emily"
+ s1.gender = "M"
+
+ with self.assertNumQueries(1):
+ s1.save()
+
+ s2 = Person.objects.get(pk=s1.pk)
+ self.assertEqual(s2.name, "Emily")
+ self.assertEqual(s2.gender, "M")
+
+ def test_update_fields_only_1(self):
+ s = Person.objects.create(name='Sara', gender='F')
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.only('name').get(pk=s.pk)
+ s1.name = "Emily"
+ s1.gender = "M"
+
+ with self.assertNumQueries(1):
+ s1.save()
+
+ s2 = Person.objects.get(pk=s1.pk)
+ self.assertEqual(s2.name, "Emily")
+ self.assertEqual(s2.gender, "M")
+
+ def test_update_fields_only_2(self):
+ s = Person.objects.create(name='Sara', gender='F', pid=22)
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.only('name').get(pk=s.pk)
+ s1.name = "Emily"
+ s1.gender = "M"
+
+ with self.assertNumQueries(2):
+ s1.save(update_fields=['pid'])
+
+ s2 = Person.objects.get(pk=s1.pk)
+ self.assertEqual(s2.name, "Sara")
+ self.assertEqual(s2.gender, "F")
+
+ def test_update_fields_only_repeated(self):
+ s = Person.objects.create(name='Sara', gender='F')
+ self.assertEqual(s.gender, 'F')
+
+ s1 = Person.objects.only('name').get(pk=s.pk)
+ s1.gender = 'M'
+ with self.assertNumQueries(1):
+ s1.save()
+ # Test that the deferred class does not remember that gender was
+ # set, instead the instace should remember this.
+ s1 = Person.objects.only('name').get(pk=s.pk)
+ with self.assertNumQueries(1):
+ s1.save()
+
+ def test_update_fields_inheritance_defer(self):
+ profile_boss = Profile.objects.create(name='Boss', salary=3000)
+ e1 = Employee.objects.create(name='Sara', gender='F',
+ employee_num=1, profile=profile_boss)
+ e1 = Employee.objects.only('name').get(pk=e1.pk)
+ e1.name = 'Linda'
+ with self.assertNumQueries(1):
+ e1.save()
+ self.assertEqual(Employee.objects.get(pk=e1.pk).name,
+ 'Linda')
+
+ def test_update_fields_fk_defer(self):
+ profile_boss = Profile.objects.create(name='Boss', salary=3000)
+ profile_receptionist = Profile.objects.create(name='Receptionist', salary=1000)
+ e1 = Employee.objects.create(name='Sara', gender='F',
+ employee_num=1, profile=profile_boss)
+ e1 = Employee.objects.only('profile').get(pk=e1.pk)
+ e1.profile = profile_receptionist
+ with self.assertNumQueries(1):
+ e1.save()
+ self.assertEqual(Employee.objects.get(pk=e1.pk).profile, profile_receptionist)
+ e1.profile_id = profile_boss.pk
+ with self.assertNumQueries(1):
+ e1.save()
+ self.assertEqual(Employee.objects.get(pk=e1.pk).profile, profile_boss)
+
+ def test_select_related_only_interaction(self):
+ profile_boss = Profile.objects.create(name='Boss', salary=3000)
+ e1 = Employee.objects.create(name='Sara', gender='F',
+ employee_num=1, profile=profile_boss)
+ e1 = Employee.objects.only('profile__salary').select_related('profile').get(pk=e1.pk)
+ profile_boss.name = 'Clerk'
+ profile_boss.salary = 1000
+ profile_boss.save()
+ # The loaded salary of 3000 gets saved, the name of 'Clerk' isn't
+ # overwritten.
+ with self.assertNumQueries(1):
+ e1.profile.save()
+ reloaded_profile = Profile.objects.get(pk=profile_boss.pk)
+ self.assertEqual(reloaded_profile.name, profile_boss.name)
+ self.assertEqual(reloaded_profile.salary, 3000)
+
def test_update_fields_m2m(self):
profile_boss = Profile.objects.create(name='Boss', salary=3000)
e1 = Employee.objects.create(name='Sara', gender='F',
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index 26fff4b863..db083290fb 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -4,6 +4,7 @@ from datetime import datetime
from django.core.exceptions import ValidationError
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
def validate_answer_to_universe(value):
@@ -66,13 +67,14 @@ class Article(models.Model):
if self.pub_date is None:
self.pub_date = datetime.now()
+@python_2_unicode_compatible
class Post(models.Model):
title = models.CharField(max_length=50, unique_for_date='posted', blank=True)
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()
- def __unicode__(self):
+ def __str__(self):
return self.name
class FlexibleDatePost(models.Model):
@@ -89,6 +91,8 @@ class GenericIPAddressTestModel(models.Model):
generic_ip = models.GenericIPAddressField(blank=True, null=True, unique=True)
v4_ip = models.GenericIPAddressField(blank=True, null=True, protocol="ipv4")
v6_ip = models.GenericIPAddressField(blank=True, null=True, protocol="ipv6")
+ ip_verbose_name = models.GenericIPAddressField("IP Address Verbose",
+ blank=True, null=True)
class GenericIPAddrUnpackUniqueTest(models.Model):
generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4=True)
diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py
index 4f658203df..58b7b94e7f 100644
--- a/tests/modeltests/validation/tests.py
+++ b/tests/modeltests/validation/tests.py
@@ -79,7 +79,7 @@ class ModelFormsTests(TestCase):
'pub_date': '2010-1-10 14:49:00'
}
form = ArticleForm(data)
- self.assertEqual(form.errors.keys(), [])
+ self.assertEqual(list(form.errors), [])
article = form.save(commit=False)
article.author = self.author
article.save()
@@ -95,7 +95,7 @@ class ModelFormsTests(TestCase):
}
article = Article(author_id=self.author.id)
form = ArticleForm(data, instance=article)
- self.assertEqual(form.errors.keys(), [])
+ self.assertEqual(list(form.errors), [])
self.assertNotEqual(form.instance.pub_date, None)
article = form.save()
@@ -108,7 +108,7 @@ class ModelFormsTests(TestCase):
}
article = Article(author_id=self.author.id)
form = ArticleForm(data, instance=article)
- self.assertEqual(form.errors.keys(), ['pub_date'])
+ self.assertEqual(list(form.errors), ['pub_date'])
class GenericIPAddressFieldTests(ValidationTestCase):
diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py
index 487db50689..4ba2f9c503 100644
--- a/tests/regressiontests/admin_changelist/models.py
+++ b/tests/regressiontests/admin_changelist/models.py
@@ -1,4 +1,5 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class Event(models.Model):
# Oracle can have problems with a column named "date"
@@ -20,17 +21,19 @@ class Band(models.Model):
nr_of_members = models.PositiveIntegerField()
genres = models.ManyToManyField(Genre)
+@python_2_unicode_compatible
class Musician(models.Model):
name = models.CharField(max_length=30)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Group(models.Model):
name = models.CharField(max_length=30)
members = models.ManyToManyField(Musician, through='Membership')
- def __unicode__(self):
+ def __str__(self):
return self.name
class Membership(models.Model):
diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
index 1ed963aaf2..be88c9a161 100644
--- a/tests/regressiontests/admin_changelist/tests.py
+++ b/tests/regressiontests/admin_changelist/tests.py
@@ -317,7 +317,7 @@ class ChangeListTests(TestCase):
m.list_editable, m)
self.assertEqual(cl.query_set.count(), 60)
self.assertEqual(cl.paginator.count, 60)
- self.assertEqual(cl.paginator.page_range, [1, 2, 3, 4, 5, 6])
+ self.assertEqual(list(cl.paginator.page_range), [1, 2, 3, 4, 5, 6])
# Test custom queryset
m = FilteredChildAdmin(Child, admin.site)
@@ -327,7 +327,7 @@ class ChangeListTests(TestCase):
m.list_editable, m)
self.assertEqual(cl.query_set.count(), 30)
self.assertEqual(cl.paginator.count, 30)
- self.assertEqual(cl.paginator.page_range, [1, 2, 3])
+ self.assertEqual(list(cl.paginator.page_range), [1, 2, 3])
def test_computed_list_display_localization(self):
"""
diff --git a/tests/regressiontests/admin_custom_urls/fixtures/actions.json b/tests/regressiontests/admin_custom_urls/fixtures/actions.json
index d803393a12..a63cf8135c 100644
--- a/tests/regressiontests/admin_custom_urls/fixtures/actions.json
+++ b/tests/regressiontests/admin_custom_urls/fixtures/actions.json
@@ -40,5 +40,12 @@
"fields": {
"description": "An action with a name suspected of being a XSS attempt"
}
+ },
+ {
+ "pk": "The name of an action",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "A generic action"
+ }
}
-] \ No newline at end of file
+]
diff --git a/tests/regressiontests/admin_custom_urls/models.py b/tests/regressiontests/admin_custom_urls/models.py
index facb4b52e7..a5b4983b09 100644
--- a/tests/regressiontests/admin_custom_urls/models.py
+++ b/tests/regressiontests/admin_custom_urls/models.py
@@ -2,13 +2,15 @@ from functools import update_wrapper
from django.contrib import admin
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Action(models.Model):
name = models.CharField(max_length=50, primary_key=True)
description = models.CharField(max_length=70)
- def __unicode__(self):
+ def __str__(self):
return self.name
@@ -26,7 +28,7 @@ class ActionAdmin(admin.ModelAdmin):
Remove all entries named 'name' from the ModelAdmin instance URL
patterns list
"""
- return filter(lambda e: e.name != name, super(ActionAdmin, self).get_urls())
+ return [url for url in super(ActionAdmin, self).get_urls() if url.name != name]
def get_urls(self):
# Add the URL of our custom 'add_view' view to the front of the URLs
diff --git a/tests/regressiontests/admin_custom_urls/tests.py b/tests/regressiontests/admin_custom_urls/tests.py
index 4a6235e7d5..64ff9f6692 100644
--- a/tests/regressiontests/admin_custom_urls/tests.py
+++ b/tests/regressiontests/admin_custom_urls/tests.py
@@ -10,6 +10,12 @@ from .models import Action
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminCustomUrlsTest(TestCase):
+ """
+ Remember that:
+ * The Action model has a CharField PK.
+ * The ModelAdmin for Action customizes the add_view URL, it's
+ '<app name>/<model name>/!add/'
+ """
fixtures = ['users.json', 'actions.json']
def setUp(self):
@@ -20,23 +26,24 @@ class AdminCustomUrlsTest(TestCase):
def testBasicAddGet(self):
"""
- A smoke test to ensure GET on the add_view works.
+ Ensure GET on the add_view works.
"""
response = self.client.get('/custom_urls/admin/admin_custom_urls/action/!add/')
self.assertIsInstance(response, TemplateResponse)
self.assertEqual(response.status_code, 200)
def testAddWithGETArgs(self):
+ """
+ Ensure GET on the add_view plus specifying a field value in the query
+ string works.
+ """
response = self.client.get('/custom_urls/admin/admin_custom_urls/action/!add/', {'name': 'My Action'})
self.assertEqual(response.status_code, 200)
- self.assertTrue(
- 'value="My Action"' in response.content,
- "Couldn't find an input with the right value in the response."
- )
+ self.assertContains(response, 'value="My Action"')
def testBasicAddPost(self):
"""
- A smoke test to ensure POST on add_view works.
+ Ensure POST on add_view works.
"""
post_data = {
'_popup': '1',
@@ -50,8 +57,7 @@ class AdminCustomUrlsTest(TestCase):
def testAdminUrlsNoClash(self):
"""
- Test that some admin URLs work correctly. The model has a CharField
- PK and the add_view URL has been customized.
+ Test that some admin URLs work correctly.
"""
# Should get the change_view for model instance with PK 'add', not show
# the add_view
@@ -60,17 +66,28 @@ class AdminCustomUrlsTest(TestCase):
self.assertContains(response, 'Change action')
# Ditto, but use reverse() to build the URL
- path = reverse('admin:%s_action_change' % Action._meta.app_label,
+ url = reverse('admin:%s_action_change' % Action._meta.app_label,
args=('add',))
- response = self.client.get(path)
+ response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Change action')
# Should correctly get the change_view for the model instance with the
- # funny-looking PK
- path = reverse('admin:%s_action_change' % Action._meta.app_label,
+ # funny-looking PK (the one wth a 'path/to/html/document.html' value)
+ url = reverse('admin:%s_action_change' % Action._meta.app_label,
args=("path/to/html/document.html",))
- response = self.client.get(path)
+ response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Change action')
self.assertContains(response, 'value="path/to/html/document.html"')
+
+ def testChangeViewHistoryButton(self):
+ url = reverse('admin:%s_action_change' % Action._meta.app_label,
+ args=('The name of an action',))
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ expected_link = reverse('admin:%s_action_history' %
+ Action._meta.app_label,
+ args=('The name of an action',))
+ self.assertContains(response, '<a href="%s" class="historylink"' %
+ expected_link)
diff --git a/tests/regressiontests/admin_filters/models.py b/tests/regressiontests/admin_filters/models.py
index 371c67061f..e0b8bde2de 100644
--- a/tests/regressiontests/admin_filters/models.py
+++ b/tests/regressiontests/admin_filters/models.py
@@ -2,8 +2,10 @@ from __future__ import unicode_literals
from django.contrib.auth.models import User
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=50)
year = models.PositiveIntegerField(null=True, blank=True)
@@ -13,20 +15,22 @@ class Book(models.Model):
date_registered = models.DateField(null=True)
no = models.IntegerField(verbose_name='number', blank=True, null=True) # This field is intentionally 2 characters long. See #16080.
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class Department(models.Model):
code = models.CharField(max_length=4, unique=True)
description = models.CharField(max_length=50, blank=True, null=True)
- def __unicode__(self):
+ def __str__(self):
return self.description
+@python_2_unicode_compatible
class Employee(models.Model):
department = models.ForeignKey(Department, to_field="code")
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
index 5a0f4d84b9..5b703a7481 100644
--- a/tests/regressiontests/admin_inlines/models.py
+++ b/tests/regressiontests/admin_inlines/models.py
@@ -7,22 +7,26 @@ from __future__ import unicode_literals
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Parent(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Teacher(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Child(models.Model):
name = models.CharField(max_length=50)
teacher = models.ForeignKey(Teacher)
@@ -31,7 +35,7 @@ class Child(models.Model):
object_id = models.PositiveIntegerField()
parent = generic.GenericForeignKey()
- def __unicode__(self):
+ def __str__(self):
return 'I am %s, a child of %s' % (self.name, self.parent)
diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt b/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt
new file mode 100644
index 0000000000..873eaded88
--- /dev/null
+++ b/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-18091-non-ascii-template.txt
@@ -0,0 +1,2 @@
+Some non-ASCII text for testing ticket #18091:
+üäö €
diff --git a/tests/regressiontests/admin_scripts/models.py b/tests/regressiontests/admin_scripts/models.py
index da803d0174..cd96052098 100644
--- a/tests/regressiontests/admin_scripts/models.py
+++ b/tests/regressiontests/admin_scripts/models.py
@@ -1,11 +1,13 @@
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, default='Default headline')
pub_date = models.DateTimeField()
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py
index 546fa7d79c..2710f80929 100644
--- a/tests/regressiontests/admin_scripts/tests.py
+++ b/tests/regressiontests/admin_scripts/tests.py
@@ -1,8 +1,10 @@
+# -*- coding: utf-8 -*-
"""
A series of tests to establish that the command-line managment tools work as
advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODULE
and default settings.py files.
"""
+from __future__ import unicode_literals
import os
import re
@@ -10,6 +12,7 @@ import shutil
import socket
import subprocess
import sys
+import codecs
from django import conf, bin, get_version
from django.conf import settings
@@ -71,6 +74,10 @@ class AdminScriptTestCase(unittest.TestCase):
os.remove(full_name + 'c')
except OSError:
pass
+ # Also remove a __pycache__ directory, if it exists
+ cache_name = os.path.join(test_dir, '__pycache__')
+ if os.path.isdir(cache_name):
+ shutil.rmtree(cache_name)
def _ext_backend_paths(self):
"""
@@ -110,14 +117,11 @@ class AdminScriptTestCase(unittest.TestCase):
python_path.extend(ext_backend_base_dirs)
os.environ[python_path_var_name] = os.pathsep.join(python_path)
- # Silence the DeprecationWarning caused by having a locale directory
- # in the project directory.
- cmd = [sys.executable, '-Wignore:::django.utils.translation', script]
-
# Move to the test directory and run
os.chdir(test_dir)
- out, err = subprocess.Popen(cmd + args,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+ 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:
@@ -1572,3 +1576,17 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
self.assertOutput(err, "Destination directory '%s' does not exist, please create it first." % testproject_dir)
self.assertFalse(os.path.exists(testproject_dir))
+
+ 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')
+ args = ['startproject', '--template', template_path, '--extension=txt', 'customtestproject']
+ testproject_dir = os.path.join(test_dir, 'customtestproject')
+
+ out, err = self.run_django_admin(args)
+ self.addCleanup(shutil.rmtree, testproject_dir)
+ self.assertNoOutput(err)
+ self.assertTrue(os.path.isdir(testproject_dir))
+ path = os.path.join(testproject_dir, 'ticket-18091-non-ascii-template.txt')
+ self.assertEqual(codecs.open(path, 'r', 'utf-8').read(),
+ 'Some non-ASCII text for testing ticket #18091:\nüäö €\n')
diff --git a/tests/regressiontests/admin_util/models.py b/tests/regressiontests/admin_util/models.py
index 5541097022..b3504a1fa4 100644
--- a/tests/regressiontests/admin_util/models.py
+++ b/tests/regressiontests/admin_util/models.py
@@ -1,5 +1,6 @@
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class Article(models.Model):
@@ -18,11 +19,12 @@ class Article(models.Model):
return "nothing"
test_from_model_with_override.short_description = "not What you Expect"
+@python_2_unicode_compatible
class Count(models.Model):
num = models.PositiveSmallIntegerField()
parent = models.ForeignKey('self', null=True)
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.num)
class Event(models.Model):
diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py
index 6b6dad4336..d04740ce95 100644
--- a/tests/regressiontests/admin_util/tests.py
+++ b/tests/regressiontests/admin_util/tests.py
@@ -171,7 +171,7 @@ class UtilTests(unittest.TestCase):
)
self.assertEqual(
label_for_field("__str__", Article),
- b"article"
+ str("article")
)
self.assertRaises(
diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py
index 5e080a9232..d23849a2a8 100644
--- a/tests/regressiontests/admin_validation/models.py
+++ b/tests/regressiontests/admin_validation/models.py
@@ -3,12 +3,14 @@ Tests of ModelAdmin validation logic.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class Album(models.Model):
title = models.CharField(max_length=150)
+@python_2_unicode_compatible
class Song(models.Model):
title = models.CharField(max_length=150)
album = models.ForeignKey(Album)
@@ -17,7 +19,7 @@ class Song(models.Model):
class Meta:
ordering = ('title',)
- def __unicode__(self):
+ def __str__(self):
return self.title
def readonly_method_on_model(self):
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index aee193b572..0d5e327ecf 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -10,6 +10,7 @@ from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.core.files.storage import FileSystemStorage
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class Section(models.Model):
@@ -20,6 +21,7 @@ class Section(models.Model):
name = models.CharField(max_length=100)
+@python_2_unicode_compatible
class Article(models.Model):
"""
A simple article to test admin views. Test backwards compatibility.
@@ -29,7 +31,7 @@ class Article(models.Model):
date = models.DateTimeField()
section = models.ForeignKey(Section, null=True, blank=True)
- def __unicode__(self):
+ def __str__(self):
return self.title
def model_year(self):
@@ -38,30 +40,33 @@ class Article(models.Model):
model_year.short_description = ''
+@python_2_unicode_compatible
class Book(models.Model):
"""
A simple book that has chapters.
"""
name = models.CharField(max_length=100, verbose_name='¿Name?')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Promo(models.Model):
name = models.CharField(max_length=100, verbose_name='¿Name?')
book = models.ForeignKey(Book)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Chapter(models.Model):
title = models.CharField(max_length=100, verbose_name='¿Title?')
content = models.TextField()
book = models.ForeignKey(Book)
- def __unicode__(self):
+ def __str__(self):
return self.title
class Meta:
@@ -69,19 +74,21 @@ class Chapter(models.Model):
verbose_name = '¿Chapter?'
+@python_2_unicode_compatible
class ChapterXtra1(models.Model):
chap = models.OneToOneField(Chapter, verbose_name='¿Chap?')
xtra = models.CharField(max_length=100, verbose_name='¿Xtra?')
- def __unicode__(self):
+ def __str__(self):
return '¿Xtra1: %s' % self.xtra
+@python_2_unicode_compatible
class ChapterXtra2(models.Model):
chap = models.OneToOneField(Chapter, verbose_name='¿Chap?')
xtra = models.CharField(max_length=100, verbose_name='¿Xtra?')
- def __unicode__(self):
+ def __str__(self):
return '¿Xtra2: %s' % self.xtra
@@ -94,20 +101,22 @@ class CustomArticle(models.Model):
date = models.DateTimeField()
+@python_2_unicode_compatible
class ModelWithStringPrimaryKey(models.Model):
string_pk = models.CharField(max_length=255, primary_key=True)
- def __unicode__(self):
+ def __str__(self):
return self.string_pk
def get_absolute_url(self):
return '/dummy/%s/' % self.string_pk
+@python_2_unicode_compatible
class Color(models.Model):
value = models.CharField(max_length=10)
warm = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return self.value
# we replicate Color to register with another ModelAdmin
@@ -115,29 +124,33 @@ class Color2(Color):
class Meta:
proxy = True
+@python_2_unicode_compatible
class Thing(models.Model):
title = models.CharField(max_length=20)
color = models.ForeignKey(Color, limit_choices_to={'warm': True})
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class Actor(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Inquisition(models.Model):
expected = models.BooleanField()
leader = models.ForeignKey(Actor)
country = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return "by %s from %s" % (self.leader, self.country)
+@python_2_unicode_compatible
class Sketch(models.Model):
title = models.CharField(max_length=100)
inquisition = models.ForeignKey(Inquisition, limit_choices_to={'leader__name': 'Palin',
@@ -145,7 +158,7 @@ class Sketch(models.Model):
'expected': False,
})
- def __unicode__(self):
+ def __str__(self):
return self.title
@@ -161,6 +174,7 @@ class Fabric(models.Model):
surface = models.CharField(max_length=20, choices=NG_CHOICES)
+@python_2_unicode_compatible
class Person(models.Model):
GENDER_CHOICES = (
(1, "Male"),
@@ -171,20 +185,22 @@ class Person(models.Model):
age = models.IntegerField(default=21)
alive = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Persona(models.Model):
"""
A simple persona associated with accounts, to test inlining of related
accounts which inherit from a common accounts class.
"""
name = models.CharField(blank=False, max_length=80)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Account(models.Model):
"""
A simple, generic account encapsulating the information shared by all
@@ -194,7 +210,7 @@ class Account(models.Model):
persona = models.ForeignKey(Persona, related_name="accounts")
servicename = 'generic service'
- def __unicode__(self):
+ def __str__(self):
return "%s: %s" % (self.servicename, self.username)
@@ -208,11 +224,12 @@ class BarAccount(Account):
servicename = 'bar'
+@python_2_unicode_compatible
class Subscriber(models.Model):
name = models.CharField(blank=False, max_length=80)
email = models.EmailField(blank=False, max_length=175)
- def __unicode__(self):
+ def __str__(self):
return "%s (%s)" % (self.name, self.email)
@@ -249,8 +266,9 @@ class Child(models.Model):
name = models.CharField(max_length=30, blank=True)
+@python_2_unicode_compatible
class EmptyModel(models.Model):
- def __unicode__(self):
+ def __str__(self):
return "Primary key = %s" % self.id
@@ -332,6 +350,7 @@ class FancyDoodad(Doodad):
expensive = models.BooleanField(default=True)
+@python_2_unicode_compatible
class Category(models.Model):
collector = models.ForeignKey(Collector)
order = models.PositiveIntegerField()
@@ -339,7 +358,7 @@ class Category(models.Model):
class Meta:
ordering = ('order',)
- def __unicode__(self):
+ def __str__(self):
return '%s:o%s' % (self.id, self.order)
@@ -376,17 +395,19 @@ class Post(models.Model):
return "Very awesome."
+@python_2_unicode_compatible
class Gadget(models.Model):
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Villain(models.Model):
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
@@ -394,6 +415,7 @@ class SuperVillain(Villain):
pass
+@python_2_unicode_compatible
class FunkyTag(models.Model):
"Because we all know there's only one real use case for GFKs."
name = models.CharField(max_length=25)
@@ -401,59 +423,65 @@ class FunkyTag(models.Model):
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Plot(models.Model):
name = models.CharField(max_length=100)
team_leader = models.ForeignKey(Villain, related_name='lead_plots')
contact = models.ForeignKey(Villain, related_name='contact_plots')
tags = generic.GenericRelation(FunkyTag)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class PlotDetails(models.Model):
details = models.CharField(max_length=100)
plot = models.OneToOneField(Plot)
- def __unicode__(self):
+ def __str__(self):
return self.details
+@python_2_unicode_compatible
class SecretHideout(models.Model):
""" Secret! Not registered with the admin! """
location = models.CharField(max_length=100)
villain = models.ForeignKey(Villain)
- def __unicode__(self):
+ def __str__(self):
return self.location
+@python_2_unicode_compatible
class SuperSecretHideout(models.Model):
""" Secret! Not registered with the admin! """
location = models.CharField(max_length=100)
supervillain = models.ForeignKey(SuperVillain)
- def __unicode__(self):
+ def __str__(self):
return self.location
+@python_2_unicode_compatible
class CyclicOne(models.Model):
name = models.CharField(max_length=25)
two = models.ForeignKey('CyclicTwo')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class CyclicTwo(models.Model):
name = models.CharField(max_length=25)
one = models.ForeignKey(CyclicOne)
- def __unicode__(self):
+ def __str__(self):
return self.name
@@ -484,11 +512,12 @@ class Question(models.Model):
question = models.CharField(max_length=20)
+@python_2_unicode_compatible
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.PROTECT)
answer = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return self.answer
@@ -523,11 +552,12 @@ class Paper(models.Model):
author = models.CharField(max_length=30, blank=True, null=True)
+@python_2_unicode_compatible
class CoverLetter(models.Model):
author = models.CharField(max_length=30)
date_written = models.DateField(null=True, blank=True)
- def __unicode__(self):
+ def __str__(self):
return self.author
@@ -575,10 +605,11 @@ class AdminOrderedCallable(models.Model):
order = models.IntegerField()
stuff = models.CharField(max_length=200)
+@python_2_unicode_compatible
class Report(models.Model):
title = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.title
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 09be55baba..cf7d4855fb 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -30,7 +30,7 @@ from django.template.response import TemplateResponse
from django.test import TestCase
from django.utils import formats, translation, unittest
from django.utils.cache import get_max_age
-from django.utils.encoding import iri_to_uri
+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 import six
@@ -78,12 +78,22 @@ class AdminViewBasicTest(TestCase):
self.client.logout()
formats.reset_format_cache()
+ def assertContentBefore(self, response, text1, text2, failing_msg=None):
+ """
+ Testing utility asserting that text1 appears before text2 in response
+ content.
+ """
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(response.content.index(force_bytes(text1)) < response.content.index(force_bytes(text2)),
+ failing_msg
+ )
+
def testTrailingSlashRequired(self):
"""
If you leave off the trailing slash, app should redirect and add it.
"""
- request = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
- self.assertRedirects(request,
+ response = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
+ self.assertRedirects(response,
'/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
)
@@ -219,12 +229,10 @@ class AdminViewBasicTest(TestCase):
(column 2 is callable_year in ArticleAdmin)
"""
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'o': 2})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index('Oldest content') < response.content.index('Middle content') and
- response.content.index('Middle content') < response.content.index('Newest content'),
- "Results of sorting on callable are out of order."
- )
+ self.assertContentBefore(response, 'Oldest content', 'Middle content',
+ "Results of sorting on callable are out of order.")
+ self.assertContentBefore(response, 'Middle content', 'Newest content',
+ "Results of sorting on callable are out of order.")
def testChangeListSortingModel(self):
"""
@@ -232,12 +240,10 @@ class AdminViewBasicTest(TestCase):
(colunn 3 is 'model_year' in ArticleAdmin)
"""
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'o': '-3'})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index('Newest content') < response.content.index('Middle content') and
- response.content.index('Middle content') < response.content.index('Oldest content'),
- "Results of sorting on Model method are out of order."
- )
+ self.assertContentBefore(response, 'Newest content', 'Middle content',
+ "Results of sorting on Model method are out of order.")
+ self.assertContentBefore(response, 'Middle content', 'Oldest content',
+ "Results of sorting on Model method are out of order.")
def testChangeListSortingModelAdmin(self):
"""
@@ -245,12 +251,10 @@ class AdminViewBasicTest(TestCase):
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
"""
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'o': '4'})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index('Oldest content') < response.content.index('Middle content') and
- response.content.index('Middle content') < response.content.index('Newest content'),
- "Results of sorting on ModelAdmin method are out of order."
- )
+ self.assertContentBefore(response, 'Oldest content', 'Middle content',
+ "Results of sorting on ModelAdmin method are out of order.")
+ self.assertContentBefore(response, 'Middle content', 'Newest content',
+ "Results of sorting on ModelAdmin method are out of order.")
def testChangeListSortingMultiple(self):
p1 = Person.objects.create(name="Chris", gender=1, alive=True)
@@ -262,19 +266,13 @@ class AdminViewBasicTest(TestCase):
# This hard-codes the URL because it'll fail if it runs against the
# 'admin2' custom admin (which doesn't have the Person model).
response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '1.2'})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % p3.id) < response.content.index(link % p1.id) and
- response.content.index(link % p1.id) < response.content.index(link % p2.id)
- )
+ self.assertContentBefore(response, link % p3.id, link % p1.id)
+ self.assertContentBefore(response, link % p1.id, link % p2.id)
# Sort by gender descending, name
response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '-2.1'})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % p2.id) < response.content.index(link % p3.id) and
- response.content.index(link % p3.id) < response.content.index(link % p1.id)
- )
+ self.assertContentBefore(response, link % p2.id, link % p3.id)
+ self.assertContentBefore(response, link % p3.id, link % p1.id)
def testChangeListSortingPreserveQuerySetOrdering(self):
"""
@@ -291,11 +289,8 @@ class AdminViewBasicTest(TestCase):
# This hard-codes the URL because it'll fail if it runs against the
# 'admin2' custom admin (which doesn't have the Person model).
response = self.client.get('/test_admin/admin/admin_views/person/', {})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % p3.id) < response.content.index(link % p2.id) and
- response.content.index(link % p2.id) < response.content.index(link % p1.id)
- )
+ self.assertContentBefore(response, link % p3.id, link % p2.id)
+ self.assertContentBefore(response, link % p2.id, link % p1.id)
def testChangeListSortingModelMeta(self):
# Test ordering on Model Meta is respected
@@ -305,17 +300,11 @@ class AdminViewBasicTest(TestCase):
link = '<a href="%s/'
response = self.client.get('/test_admin/admin/admin_views/language/', {})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % l2.pk) < response.content.index(link % l1.pk)
- )
+ self.assertContentBefore(response, link % l2.pk, link % l1.pk)
# Test we can override with query string
response = self.client.get('/test_admin/admin/admin_views/language/', {'o':'-1'})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % l1.pk) < response.content.index(link % l2.pk)
- )
+ self.assertContentBefore(response, link % l1.pk, link % l2.pk)
def testChangeListSortingOverrideModelAdmin(self):
# Test ordering on Model Admin is respected, and overrides Model Meta
@@ -325,10 +314,7 @@ class AdminViewBasicTest(TestCase):
link = '<a href="%s/'
response = self.client.get('/test_admin/admin/admin_views/podcast/', {})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % p1.pk) < response.content.index(link % p2.pk)
- )
+ self.assertContentBefore(response, link % p1.pk, link % p2.pk)
def testMultipleSortSameField(self):
# Check that we get the columns we expect if we have two columns
@@ -339,17 +325,13 @@ class AdminViewBasicTest(TestCase):
link = '<a href="%s/'
response = self.client.get('/test_admin/admin/admin_views/podcast/', {})
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- response.content.index(link % p1.pk) < response.content.index(link % p2.pk)
- )
+ self.assertContentBefore(response, link % p1.pk, link % p2.pk)
p1 = ComplexSortedPerson.objects.create(name="Bob", age=10)
p2 = ComplexSortedPerson.objects.create(name="Amy", age=20)
link = '<a href="%s/'
response = self.client.get('/test_admin/admin/admin_views/complexsortedperson/', {})
- self.assertEqual(response.status_code, 200)
# Should have 5 columns (including action checkbox col)
self.assertContains(response, '<th scope="col"', count=5)
@@ -357,14 +339,10 @@ class AdminViewBasicTest(TestCase):
self.assertContains(response, 'Colored name')
# Check order
- self.assertTrue(
- response.content.index('Name') < response.content.index('Colored name')
- )
+ self.assertContentBefore(response, 'Name', 'Colored name')
# Check sorting - should be by name
- self.assertTrue(
- response.content.index(link % p2.id) < response.content.index(link % p1.id)
- )
+ self.assertContentBefore(response, link % p2.id, link % p1.id)
def testSortIndicatorsAdminOrder(self):
"""
@@ -389,8 +367,8 @@ class AdminViewBasicTest(TestCase):
# the implicit 'action_checkbox' and 1 being the column 'stuff'.
self.assertEqual(response.context['cl'].get_ordering_field_columns(), {2: 'asc'})
# Check order of records.
- self.assertTrue(response.content.index('The First Item') <
- response.content.index('The Middle Item') < response.content.index('The Last Item'))
+ self.assertContentBefore(response, 'The First Item', 'The Middle Item')
+ self.assertContentBefore(response, 'The Middle Item', 'The Last Item')
def testLimitedFilter(self):
"""Ensure admin changelist filters do not contain objects excluded via limit_choices_to.
@@ -480,12 +458,9 @@ class AdminViewBasicTest(TestCase):
has been used in the choices option of a field.
"""
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
- self.assertEqual(response.status_code, 200)
- self.assertTrue(
- '<a href="1/">Horizontal</a>' in response.content and
- '<a href="2/">Vertical</a>' in response.content,
- "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
- )
+ fail_msg = "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
+ self.assertContains(response, '<a href="1/">Horizontal</a>', msg_prefix=fail_msg, html=True)
+ self.assertContains(response, '<a href="2/">Vertical</a>', msg_prefix=fail_msg, html=True)
def testNamedGroupFieldChoicesFilter(self):
"""
@@ -493,13 +468,12 @@ class AdminViewBasicTest(TestCase):
been used in the choices option of a model field.
"""
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
- self.assertEqual(response.status_code, 200)
+ fail_msg = "Changelist filter isn't showing options contained inside a model field 'choices' option named group."
self.assertContains(response, '<div id="changelist-filter">')
- self.assertTrue(
- '<a href="?surface__exact=x">Horizontal</a>' in response.content and
- '<a href="?surface__exact=y">Vertical</a>' in response.content,
- "Changelist filter isn't showing options contained inside a model field 'choices' option named group."
- )
+ self.assertContains(response,
+ '<a href="?surface__exact=x">Horizontal</a>', msg_prefix=fail_msg, html=True)
+ self.assertContains(response,
+ '<a href="?surface__exact=y">Vertical</a>', msg_prefix=fail_msg, html=True)
def testChangeListNullBooleanDisplay(self):
Post.objects.create(public=None)
@@ -590,8 +564,8 @@ class AdminViewBasicTest(TestCase):
user.save()
response = self.client.get('/test_admin/admin/')
- self.assertFalse(reverse('admin:password_change') in response.content,
- msg='The "change password" link should not be displayed if a user does not have a usable password.')
+ self.assertNotContains(response, reverse('admin:password_change'),
+ msg_prefix='The "change password" link should not be displayed if a user does not have a usable password.')
def test_change_view_with_show_delete_extra_context(self):
"""
@@ -618,7 +592,7 @@ class AdminViewFormUrlTest(TestCase):
def testChangeFormUrlHasCorrectValue(self):
"""
- Tests whether change_view has form_url in request.context
+ Tests whether change_view has form_url in response.context
"""
response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
self.assertTrue('form_url' in response.context, msg='form_url not present in response.context')
@@ -755,39 +729,39 @@ class CustomModelAdminTest(AdminViewBasicTest):
def testCustomAdminSiteLoginTemplate(self):
self.client.logout()
- request = self.client.get('/test_admin/admin2/')
- self.assertIsInstance(request, TemplateResponse)
- self.assertTemplateUsed(request, 'custom_admin/login.html')
- self.assertTrue('Hello from a custom login template' in request.content)
+ response = self.client.get('/test_admin/admin2/')
+ self.assertIsInstance(response, TemplateResponse)
+ self.assertTemplateUsed(response, 'custom_admin/login.html')
+ self.assertContains(response, 'Hello from a custom login template')
def testCustomAdminSiteLogoutTemplate(self):
- request = self.client.get('/test_admin/admin2/logout/')
- self.assertIsInstance(request, TemplateResponse)
- self.assertTemplateUsed(request, 'custom_admin/logout.html')
- self.assertTrue('Hello from a custom logout template' in request.content)
+ response = self.client.get('/test_admin/admin2/logout/')
+ self.assertIsInstance(response, TemplateResponse)
+ self.assertTemplateUsed(response, 'custom_admin/logout.html')
+ self.assertContains(response, 'Hello from a custom logout template')
def testCustomAdminSiteIndexViewAndTemplate(self):
- request = self.client.get('/test_admin/admin2/')
- self.assertIsInstance(request, TemplateResponse)
- self.assertTemplateUsed(request, 'custom_admin/index.html')
- self.assertTrue('Hello from a custom index template *bar*' in request.content)
+ response = self.client.get('/test_admin/admin2/')
+ self.assertIsInstance(response, TemplateResponse)
+ self.assertTemplateUsed(response, 'custom_admin/index.html')
+ self.assertContains(response, 'Hello from a custom index template *bar*')
def testCustomAdminSitePasswordChangeTemplate(self):
- request = self.client.get('/test_admin/admin2/password_change/')
- self.assertIsInstance(request, TemplateResponse)
- self.assertTemplateUsed(request, 'custom_admin/password_change_form.html')
- self.assertTrue('Hello from a custom password change form template' in request.content)
+ response = self.client.get('/test_admin/admin2/password_change/')
+ self.assertIsInstance(response, TemplateResponse)
+ self.assertTemplateUsed(response, 'custom_admin/password_change_form.html')
+ self.assertContains(response, 'Hello from a custom password change form template')
def testCustomAdminSitePasswordChangeDoneTemplate(self):
- request = self.client.get('/test_admin/admin2/password_change/done/')
- self.assertIsInstance(request, TemplateResponse)
- self.assertTemplateUsed(request, 'custom_admin/password_change_done.html')
- self.assertTrue('Hello from a custom password change done template' in request.content)
+ response = self.client.get('/test_admin/admin2/password_change/done/')
+ self.assertIsInstance(response, TemplateResponse)
+ self.assertTemplateUsed(response, 'custom_admin/password_change_done.html')
+ self.assertContains(response, 'Hello from a custom password change done template')
def testCustomAdminSiteView(self):
self.client.login(username='super', password='secret')
response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
- self.assertEqual(response.content, "Django is a magical pony!")
+ self.assertEqual(response.content, b"Django is a magical pony!")
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
@@ -885,16 +859,16 @@ class AdminViewPermissionsTest(TestCase):
a 200 status code.
"""
# Super User
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.super_login)
self.assertRedirects(login, '/test_admin/admin/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Test if user enters e-mail address
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.super_email_login)
self.assertContains(login, "Your e-mail address is not your username")
# only correct passwords get a username hint
@@ -907,47 +881,47 @@ class AdminViewPermissionsTest(TestCase):
self.assertContains(login, ERROR_MESSAGE)
# Add User
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.adduser_login)
self.assertRedirects(login, '/test_admin/admin/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Change User
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.changeuser_login)
self.assertRedirects(login, '/test_admin/admin/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Delete User
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.deleteuser_login)
self.assertRedirects(login, '/test_admin/admin/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Regular User should not be able to login.
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.joepublic_login)
self.assertEqual(login.status_code, 200)
self.assertContains(login, ERROR_MESSAGE)
# Requests without username should not return 500 errors.
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.no_username_login)
self.assertEqual(login.status_code, 200)
form = login.context[0].get('form')
self.assertEqual(form.errors['username'][0], 'This field is required.')
def testLoginSuccessfullyRedirectsToOriginalUrl(self):
- request = self.client.get('/test_admin/admin/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/')
+ self.assertEqual(response.status_code, 200)
query_string = 'the-answer=42'
redirect_url = '/test_admin/admin/?%s' % query_string
new_next = {REDIRECT_FIELD_NAME: redirect_url}
@@ -967,8 +941,8 @@ class AdminViewPermissionsTest(TestCase):
self.client.post('/test_admin/admin/', self.changeuser_login)
# make sure the view removes test cookie
self.assertEqual(self.client.session.test_cookie_worked(), False)
- request = self.client.get('/test_admin/admin/admin_views/article/add/')
- self.assertEqual(request.status_code, 403)
+ response = self.client.get('/test_admin/admin/admin_views/article/add/')
+ self.assertEqual(response.status_code, 403)
# Try POST just to make sure
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
self.assertEqual(post.status_code, 403)
@@ -979,10 +953,9 @@ class AdminViewPermissionsTest(TestCase):
self.client.get('/test_admin/admin/')
self.client.post('/test_admin/admin/', self.adduser_login)
addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
- self.assertEqual(addpage.status_code, 200)
change_list_link = '&rsaquo; <a href="/test_admin/admin/admin_views/article/">Articles</a>'
- self.assertFalse(change_list_link in addpage.content,
- 'User restricted to add permission is given link to change list view in breadcrumbs.')
+ self.assertNotContains(addpage, change_list_link,
+ msg_prefix='User restricted to add permission is given link to change list view in breadcrumbs.')
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
self.assertRedirects(post, '/test_admin/admin/')
self.assertEqual(Article.objects.all().count(), 4)
@@ -994,9 +967,8 @@ class AdminViewPermissionsTest(TestCase):
self.client.get('/test_admin/admin/')
self.client.post('/test_admin/admin/', self.super_login)
addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
- self.assertEqual(addpage.status_code, 200)
- self.assertFalse(change_list_link not in addpage.content,
- 'Unrestricted user is not given link to change list view in breadcrumbs.')
+ self.assertContains(addpage, change_list_link,
+ msg_prefix='Unrestricted user is not given link to change list view in breadcrumbs.')
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
self.assertEqual(Article.objects.all().count(), 5)
@@ -1022,10 +994,10 @@ class AdminViewPermissionsTest(TestCase):
# add user shoud not be able to view the list of article or change any of them
self.client.get('/test_admin/admin/')
self.client.post('/test_admin/admin/', self.adduser_login)
- request = self.client.get('/test_admin/admin/admin_views/article/')
- self.assertEqual(request.status_code, 403)
- request = self.client.get('/test_admin/admin/admin_views/article/1/')
- self.assertEqual(request.status_code, 403)
+ response = self.client.get('/test_admin/admin/admin_views/article/')
+ self.assertEqual(response.status_code, 403)
+ response = self.client.get('/test_admin/admin/admin_views/article/1/')
+ self.assertEqual(response.status_code, 403)
post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
self.assertEqual(post.status_code, 403)
self.client.get('/test_admin/admin/logout/')
@@ -1033,10 +1005,10 @@ class AdminViewPermissionsTest(TestCase):
# change user can view all items and edit them
self.client.get('/test_admin/admin/')
self.client.post('/test_admin/admin/', self.changeuser_login)
- request = self.client.get('/test_admin/admin/admin_views/article/')
- self.assertEqual(request.status_code, 200)
- request = self.client.get('/test_admin/admin/admin_views/article/1/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/admin_views/article/')
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get('/test_admin/admin/admin_views/article/1/')
+ self.assertEqual(response.status_code, 200)
post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
self.assertEqual(Article.objects.get(pk=1).content, '<p>edited article</p>')
@@ -1058,33 +1030,33 @@ class AdminViewPermissionsTest(TestCase):
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)
- request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/')
- self.assertEqual(request.status_code, 403)
- request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/', {'name': 'changed'})
+ response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/')
+ self.assertEqual(response.status_code, 403)
+ response = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/', {'name': 'changed'})
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=1).name, 'odd id')
- self.assertEqual(request.status_code, 403)
- request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/')
- self.assertEqual(request.status_code, 200)
- request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/', {'name': 'changed'})
+ self.assertEqual(response.status_code, 403)
+ response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/')
+ self.assertEqual(response.status_code, 200)
+ response = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/', {'name': 'changed'})
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=2).name, 'changed')
- self.assertRedirects(request, '/test_admin/admin/')
+ self.assertRedirects(response, '/test_admin/admin/')
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)
- request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/')
- self.assertEqual(request.status_code, 200)
- self.assertContains(request, 'login-form')
- request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/', {'name': 'changed'})
+ response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'login-form')
+ response = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/', {'name': 'changed'})
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=1).name, 'odd id')
- self.assertEqual(request.status_code, 200)
- self.assertContains(request, 'login-form')
- request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/')
- self.assertEqual(request.status_code, 200)
- self.assertContains(request, 'login-form')
- request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/', {'name': 'changed again'})
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'login-form')
+ response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'login-form')
+ response = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/', {'name': 'changed again'})
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=2).name, 'changed')
- self.assertEqual(request.status_code, 200)
- self.assertContains(request, 'login-form')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'login-form')
self.client.get('/test_admin/admin/logout/')
def testConditionallyShowAddSectionLink(self):
@@ -1114,14 +1086,13 @@ class AdminViewPermissionsTest(TestCase):
self.client.post('/test_admin/admin/', self.super_login)
# Test custom change list template with custom extra context
- request = self.client.get('/test_admin/admin/admin_views/customarticle/')
- self.assertEqual(request.status_code, 200)
- self.assertTrue("var hello = 'Hello!';" in request.content)
- self.assertTemplateUsed(request, 'custom_admin/change_list.html')
+ response = self.client.get('/test_admin/admin/admin_views/customarticle/')
+ self.assertContains(response, "var hello = 'Hello!';")
+ self.assertTemplateUsed(response, 'custom_admin/change_list.html')
# Test custom add form template
- request = self.client.get('/test_admin/admin/admin_views/customarticle/add/')
- self.assertTemplateUsed(request, 'custom_admin/add_form.html')
+ response = self.client.get('/test_admin/admin/admin_views/customarticle/add/')
+ self.assertTemplateUsed(response, 'custom_admin/add_form.html')
# Add an article so we can test delete, change, and history views
post = self.client.post('/test_admin/admin/admin_views/customarticle/add/', {
@@ -1135,18 +1106,18 @@ class AdminViewPermissionsTest(TestCase):
# Test custom delete, change, and object history templates
# Test custom change form template
- request = self.client.get('/test_admin/admin/admin_views/customarticle/%d/' % article_pk)
- self.assertTemplateUsed(request, 'custom_admin/change_form.html')
- request = self.client.get('/test_admin/admin/admin_views/customarticle/%d/delete/' % article_pk)
- self.assertTemplateUsed(request, 'custom_admin/delete_confirmation.html')
- request = self.client.post('/test_admin/admin/admin_views/customarticle/', data={
+ response = self.client.get('/test_admin/admin/admin_views/customarticle/%d/' % article_pk)
+ self.assertTemplateUsed(response, 'custom_admin/change_form.html')
+ response = self.client.get('/test_admin/admin/admin_views/customarticle/%d/delete/' % article_pk)
+ self.assertTemplateUsed(response, 'custom_admin/delete_confirmation.html')
+ response = self.client.post('/test_admin/admin/admin_views/customarticle/', data={
'index': 0,
'action': ['delete_selected'],
'_selected_action': ['1'],
})
- self.assertTemplateUsed(request, 'custom_admin/delete_selected_confirmation.html')
- request = self.client.get('/test_admin/admin/admin_views/customarticle/%d/history/' % article_pk)
- self.assertTemplateUsed(request, 'custom_admin/object_history.html')
+ self.assertTemplateUsed(response, 'custom_admin/delete_selected_confirmation.html')
+ response = self.client.get('/test_admin/admin/admin_views/customarticle/%d/history/' % article_pk)
+ self.assertTemplateUsed(response, 'custom_admin/object_history.html')
self.client.get('/test_admin/admin/logout/')
@@ -1158,8 +1129,8 @@ class AdminViewPermissionsTest(TestCase):
# add user shoud not be able to delete articles
self.client.get('/test_admin/admin/')
self.client.post('/test_admin/admin/', self.adduser_login)
- request = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
- self.assertEqual(request.status_code, 403)
+ response = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
+ self.assertEqual(response.status_code, 403)
post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
self.assertEqual(post.status_code, 403)
self.assertEqual(Article.objects.all().count(), 3)
@@ -1246,9 +1217,9 @@ class AdminViewDeletedObjectsTest(TestCase):
Objects should be nested to display the relationships that
cause them to be scheduled for deletion.
"""
- pattern = re.compile(r"""<li>Plot: <a href=".+/admin_views/plot/1/">World Domination</a>\s*<ul>\s*<li>Plot details: <a href=".+/admin_views/plotdetails/1/">almost finished</a>""")
+ 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.assertTrue(pattern.search(response.content))
+ self.assertRegexpMatches(response.content, pattern)
def test_cyclic(self):
"""
@@ -1407,9 +1378,9 @@ class AdminViewStringPrimaryKeyTest(TestCase):
logentry.content_type = None
logentry.save()
- counted_presence_before = response.content.count(should_contain)
+ counted_presence_before = response.content.count(force_bytes(should_contain))
response = self.client.get('/test_admin/admin/')
- counted_presence_after = response.content.count(should_contain)
+ counted_presence_after = response.content.count(force_bytes(should_contain))
self.assertEqual(counted_presence_before - 1,
counted_presence_after)
@@ -1512,8 +1483,8 @@ class SecureViewTests(TestCase):
self.assertTemplateUsed(response, 'admin/login.html')
def test_secure_view_login_successfully_redirects_to_original_url(self):
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
query_string = 'the-answer=42'
redirect_url = '/test_admin/admin/secure-view/?%s' % query_string
new_next = {REDIRECT_FIELD_NAME: redirect_url}
@@ -1529,8 +1500,8 @@ class SecureViewTests(TestCase):
a 200 status code.
"""
# Super User
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.super_login)
self.assertRedirects(login, '/test_admin/admin/secure-view/')
self.assertFalse(login.context)
@@ -1539,8 +1510,8 @@ class SecureViewTests(TestCase):
self.assertEqual(self.client.session.test_cookie_worked(), False)
# Test if user enters e-mail address
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
self.assertContains(login, "Your e-mail address is not your username")
# only correct passwords get a username hint
@@ -1553,32 +1524,32 @@ class SecureViewTests(TestCase):
self.assertContains(login, ERROR_MESSAGE)
# Add User
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.adduser_login)
self.assertRedirects(login, '/test_admin/admin/secure-view/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Change User
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.changeuser_login)
self.assertRedirects(login, '/test_admin/admin/secure-view/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Delete User
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.deleteuser_login)
self.assertRedirects(login, '/test_admin/admin/secure-view/')
self.assertFalse(login.context)
self.client.get('/test_admin/admin/logout/')
# Regular User should not be able to login.
- request = self.client.get('/test_admin/admin/secure-view/')
- self.assertEqual(request.status_code, 200)
+ response = self.client.get('/test_admin/admin/secure-view/')
+ self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login)
self.assertEqual(login.status_code, 200)
# Login.context is a list of context dicts we just need to check the first one.
@@ -2107,7 +2078,7 @@ class AdminInheritedInlinesTest(TestCase):
foo_user = "foo username"
bar_user = "bar username"
- name_re = re.compile('name="(.*?)"')
+ name_re = re.compile(b'name="(.*?)"')
# test the add case
response = self.client.get('/test_admin/admin/admin_views/persona/add/')
@@ -2214,7 +2185,7 @@ class AdminActionsTest(TestCase):
confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
self.assertIsInstance(confirmation, TemplateResponse)
self.assertContains(confirmation, "Are you sure you want to delete the selected subscribers?")
- self.assertTrue(confirmation.content.count(ACTION_CHECKBOX_NAME) == 2)
+ self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2)
response = self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data)
self.assertEqual(Subscriber.objects.count(), 0)
@@ -2390,7 +2361,7 @@ class AdminActionsTest(TestCase):
def test_popup_actions(self):
""" Actions should not be shown in popups. """
response = self.client.get('/test_admin/admin/admin_views/subscriber/')
- self.assertNotEquals(response.context["action_form"], None)
+ self.assertNotEqual(response.context["action_form"], None)
response = self.client.get(
'/test_admin/admin/admin_views/subscriber/?%s' % IS_POPUP_VAR)
self.assertEqual(response.context["action_form"], None)
@@ -2514,7 +2485,7 @@ class AdminInlineFileUploadTest(TestCase):
# to use a NamedTemporaryFile.
tdir = tempfile.gettempdir()
file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
- file1.write('a' * (2 ** 21))
+ file1.write(b'a' * (2 ** 21))
filename = file1.name
file1.close()
self.gallery = Gallery(name="Test Gallery")
@@ -3197,9 +3168,9 @@ class RawIdFieldsTest(TestCase):
country="Spain")
response = self.client.get('/test_admin/admin/admin_views/sketch/add/')
# Find the link
- m = re.search(r'<a href="([^"]*)"[^>]* id="lookup_id_inquisition"', response.content)
+ m = re.search(br'<a href="([^"]*)"[^>]* id="lookup_id_inquisition"', response.content)
self.assertTrue(m) # Got a match
- popup_url = m.groups()[0].replace("&amp;", "&")
+ popup_url = m.groups()[0].decode().replace("&amp;", "&")
# Handle relative links
popup_url = urljoin(response.request['PATH_INFO'], popup_url)
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
index 81a4ec7aba..2e452798b7 100644
--- a/tests/regressiontests/admin_widgets/models.py
+++ b/tests/regressiontests/admin_widgets/models.py
@@ -2,39 +2,44 @@ from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
+from django.utils.encoding import python_2_unicode_compatible
class MyFileField(models.FileField):
pass
+@python_2_unicode_compatible
class Member(models.Model):
name = models.CharField(max_length=100)
birthdate = models.DateTimeField(blank=True, null=True)
gender = models.CharField(max_length=1, blank=True, choices=[('M','Male'), ('F', 'Female')])
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Band(models.Model):
name = models.CharField(max_length=100)
members = models.ManyToManyField(Member)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Album(models.Model):
band = models.ForeignKey(Band)
name = models.CharField(max_length=100)
cover_art = models.FileField(upload_to='albums')
backside_art = MyFileField(upload_to='albums_back', null=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
class HiddenInventoryManager(models.Manager):
def get_query_set(self):
return super(HiddenInventoryManager, self).get_query_set().filter(hidden=False)
+@python_2_unicode_compatible
class Inventory(models.Model):
barcode = models.PositiveIntegerField(unique=True)
parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
@@ -45,7 +50,7 @@ class Inventory(models.Model):
default_manager = models.Manager()
objects = HiddenInventoryManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
class Event(models.Model):
@@ -56,12 +61,13 @@ class Event(models.Model):
link = models.URLField(blank=True)
min_age = models.IntegerField(blank=True, null=True)
+@python_2_unicode_compatible
class Car(models.Model):
owner = models.ForeignKey(User)
make = models.CharField(max_length=30)
model = models.CharField(max_length=30)
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.make, self.model)
class CarTire(models.Model):
@@ -103,19 +109,21 @@ class Advisor(models.Model):
companies = models.ManyToManyField(Company)
+@python_2_unicode_compatible
class Student(models.Model):
name = models.CharField(max_length=255)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
ordering = ('name',)
+@python_2_unicode_compatible
class School(models.Model):
name = models.CharField(max_length=255)
students = models.ManyToManyField(Student, related_name='current_schools')
alumni = models.ManyToManyField(Student, related_name='previous_schools')
- def __unicode__(self):
- return self.name \ No newline at end of file
+ def __str__(self):
+ return self.name
diff --git a/tests/regressiontests/aggregation_regress/models.py b/tests/regressiontests/aggregation_regress/models.py
index ccef9a5fc8..dd4ff50aec 100644
--- a/tests/regressiontests/aggregation_regress/models.py
+++ b/tests/regressiontests/aggregation_regress/models.py
@@ -1,24 +1,28 @@
# coding: utf-8
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)
age = models.IntegerField()
friends = models.ManyToManyField('self', blank=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Publisher(models.Model):
name = models.CharField(max_length=255)
num_awards = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Book(models.Model):
isbn = models.CharField(max_length=9)
name = models.CharField(max_length=255)
@@ -33,17 +37,18 @@ class Book(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Store(models.Model):
name = models.CharField(max_length=255)
books = models.ManyToManyField(Book)
original_opening = models.DateTimeField()
friday_night_closing = models.TimeField()
- def __unicode__(self):
+ def __str__(self):
return self.name
class Entries(models.Model):
@@ -58,8 +63,9 @@ class Clues(models.Model):
Clue = models.CharField(max_length=150)
+@python_2_unicode_compatible
class HardbackBook(Book):
weight = models.FloatField()
- def __unicode__(self):
+ def __str__(self):
return "%s (hardback): %s" % (self.name, self.weight)
diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py
index e24eb43b87..b9f3ab27eb 100644
--- a/tests/regressiontests/aggregation_regress/tests.py
+++ b/tests/regressiontests/aggregation_regress/tests.py
@@ -866,3 +866,15 @@ class AggregationTests(TestCase):
['Peter Norvig'],
lambda b: b.name
)
+
+ def test_type_conversion(self):
+ # The database backend convert_values function should not try to covert
+ # CharFields to float. Refs #13844.
+ from django.db.models import CharField
+ from django.db import connection
+ testData = 'not_a_float_value'
+ testField = CharField()
+ self.assertEqual(
+ connection.ops.convert_values(testData, testField),
+ testData
+ )
diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py
index af4952dce8..344cf4c798 100644
--- a/tests/regressiontests/backends/models.py
+++ b/tests/regressiontests/backends/models.py
@@ -3,21 +3,24 @@ from __future__ import unicode_literals
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models, connection
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Square(models.Model):
root = models.IntegerField()
square = models.PositiveIntegerField()
- def __unicode__(self):
+ def __str__(self):
return "%s ** 2 == %s" % (self.root, self.square)
+@python_2_unicode_compatible
class Person(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
@@ -55,18 +58,20 @@ class Post(models.Model):
db_table = 'CaseSensitive_Post'
+@python_2_unicode_compatible
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter)
- def __unicode__(self):
+ def __str__(self):
return self.headline
diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
index a6425c5591..dc92189344 100644
--- a/tests/regressiontests/backends/tests.py
+++ b/tests/regressiontests/backends/tests.py
@@ -584,7 +584,7 @@ class ThreadTests(TestCase):
connections['default'] = main_thread_connection
try:
models.Person.objects.get(first_name="John", last_name="Doe")
- except DatabaseError as e:
+ except Exception as e:
exceptions.append(e)
t = threading.Thread(target=runner, args=[connections['default']])
t.start()
@@ -594,21 +594,21 @@ class ThreadTests(TestCase):
exceptions = []
do_thread()
# Forbidden!
- self.assertTrue(isinstance(exceptions[0], DatabaseError))
+ self.assertIsInstance(exceptions[0], DatabaseError)
# If explicitly setting allow_thread_sharing to False
connections['default'].allow_thread_sharing = False
exceptions = []
do_thread()
# Forbidden!
- self.assertTrue(isinstance(exceptions[0], DatabaseError))
+ self.assertIsInstance(exceptions[0], DatabaseError)
# If explicitly setting allow_thread_sharing to True
connections['default'].allow_thread_sharing = True
exceptions = []
do_thread()
# All good
- self.assertEqual(len(exceptions), 0)
+ self.assertEqual(exceptions, [])
def test_closing_non_shared_connections(self):
"""
diff --git a/tests/regressiontests/builtin_server/tests.py b/tests/regressiontests/builtin_server/tests.py
index 4a3b44176b..c8dc77e42e 100644
--- a/tests/regressiontests/builtin_server/tests.py
+++ b/tests/regressiontests/builtin_server/tests.py
@@ -1,5 +1,8 @@
+from __future__ import unicode_literals
+
+from io import BytesIO
+
from django.core.servers.basehttp import ServerHandler
-from django.utils.six import StringIO
from django.utils.unittest import TestCase
#
@@ -23,12 +26,12 @@ class FileWrapperHandler(ServerHandler):
return True
def wsgi_app(environ, start_response):
- start_response('200 OK', [('Content-Type', 'text/plain')])
- return ['Hello World!']
+ 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('200 OK', [('Content-Type', 'text/plain')])
- return environ['wsgi.file_wrapper'](StringIO('foo'))
+ start_response(str('200 OK'), [(str('Content-Type'), str('text/plain'))])
+ return environ['wsgi.file_wrapper'](BytesIO(b'foo'))
class WSGIFileWrapperTests(TestCase):
"""
@@ -37,15 +40,16 @@ class WSGIFileWrapperTests(TestCase):
def test_file_wrapper_uses_sendfile(self):
env = {'SERVER_PROTOCOL': 'HTTP/1.0'}
- err = StringIO()
- handler = FileWrapperHandler(None, StringIO(), err, env)
+ 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'}
- err = StringIO()
- handler = FileWrapperHandler(None, StringIO(), err, env)
+ handler = FileWrapperHandler(None, BytesIO(), BytesIO(), env)
handler.run(wsgi_app)
self.assertFalse(handler._used_sendfile)
- self.assertEqual(handler.stdout.getvalue().splitlines()[-1],'Hello World!')
+ self.assertEqual(handler.stdout.getvalue().splitlines()[-1], b'Hello World!')
+ self.assertEqual(handler.stderr.getvalue(), b'')
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index 8fc749aaa2..a6534c5f54 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -6,7 +6,9 @@ from __future__ import absolute_import, unicode_literals
import hashlib
import os
+import random
import re
+import string
import tempfile
import time
import warnings
@@ -364,25 +366,25 @@ class BaseCacheTests(object):
# Binary strings should be cacheable
from zlib import compress, decompress
value = 'value_to_be_compressed'
- compressed_value = compress(value)
+ compressed_value = compress(value.encode())
# Test set
self.cache.set('binary1', compressed_value)
compressed_result = self.cache.get('binary1')
self.assertEqual(compressed_value, compressed_result)
- self.assertEqual(value, decompress(compressed_result))
+ self.assertEqual(value, decompress(compressed_result).decode())
# Test add
self.cache.add('binary1-add', compressed_value)
compressed_result = self.cache.get('binary1-add')
self.assertEqual(compressed_value, compressed_result)
- self.assertEqual(value, decompress(compressed_result))
+ self.assertEqual(value, decompress(compressed_result).decode())
# Test set_many
self.cache.set_many({'binary1-set_many': compressed_value})
compressed_result = self.cache.get('binary1-set_many')
self.assertEqual(compressed_value, compressed_result)
- self.assertEqual(value, decompress(compressed_result))
+ self.assertEqual(value, decompress(compressed_result).decode())
def test_set_many(self):
# Multiple keys can be set using set_many
@@ -774,13 +776,13 @@ class BaseCacheTests(object):
get_cache_data = fetch_middleware.process_request(request)
self.assertNotEqual(get_cache_data, None)
- self.assertEqual(get_cache_data.content, content)
+ self.assertEqual(get_cache_data.content, content.encode('utf-8'))
self.assertEqual(get_cache_data.cookies, response.cookies)
update_middleware.process_response(request, get_cache_data)
get_cache_data = fetch_middleware.process_request(request)
self.assertNotEqual(get_cache_data, None)
- self.assertEqual(get_cache_data.content, content)
+ self.assertEqual(get_cache_data.content, content.encode('utf-8'))
self.assertEqual(get_cache_data.cookies, response.cookies)
def custom_key_func(key, key_prefix, version):
@@ -932,21 +934,24 @@ class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
# memcached backend isn't guaranteed to be available.
# To check the memcached backend, the test settings file will
-# need to contain a cache backend setting that points at
+# need to contain at least one cache backend setting that points at
# your memcache server.
@unittest.skipUnless(
- settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND'].startswith('django.core.cache.backends.memcached.'),
+ any(cache['BACKEND'].startswith('django.core.cache.backends.memcached.')
+ for cache in settings.CACHES.values()),
"memcached not available")
class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
- backend_name = 'django.core.cache.backends.memcached.MemcachedCache'
def setUp(self):
- name = settings.CACHES[DEFAULT_CACHE_ALIAS]['LOCATION']
- self.cache = get_cache(self.backend_name, LOCATION=name)
- self.prefix_cache = get_cache(self.backend_name, LOCATION=name, KEY_PREFIX='cacheprefix')
- self.v2_cache = get_cache(self.backend_name, LOCATION=name, VERSION=2)
- self.custom_key_cache = get_cache(self.backend_name, LOCATION=name, KEY_FUNCTION=custom_key_func)
- self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=name, KEY_FUNCTION='regressiontests.cache.tests.custom_key_func')
+ for cache_key, cache in settings.CACHES.items():
+ if cache['BACKEND'].startswith('django.core.cache.backends.memcached.'):
+ break
+ random_prefix = ''.join(random.choice(string.ascii_letters) for x in range(10))
+ self.cache = get_cache(cache_key)
+ 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')
def tearDown(self):
self.cache.clear()
@@ -988,7 +993,7 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
"""Test that keys are hashed into subdirectories correctly"""
self.cache.set("foo", "bar")
key = self.cache.make_key("foo")
- keyhash = hashlib.md5(key).hexdigest()
+ keyhash = hashlib.md5(key.encode()).hexdigest()
keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
self.assertTrue(os.path.exists(keypath))
@@ -998,7 +1003,7 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
"""
self.cache.set("foo", "bar")
key = self.cache.make_key("foo")
- keyhash = hashlib.md5(key).hexdigest()
+ keyhash = hashlib.md5(key.encode()).hexdigest()
keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
self.assertTrue(os.path.exists(keypath))
@@ -1222,7 +1227,7 @@ class CacheHEADTest(TestCase):
request = self._get_request('HEAD')
get_cache_data = FetchFromCacheMiddleware().process_request(request)
self.assertNotEqual(get_cache_data, None)
- self.assertEqual(test_content, get_cache_data.content)
+ self.assertEqual(test_content.encode(), get_cache_data.content)
def test_head_with_cached_get(self):
test_content = 'test content'
@@ -1233,7 +1238,7 @@ class CacheHEADTest(TestCase):
request = self._get_request('HEAD')
get_cache_data = FetchFromCacheMiddleware().process_request(request)
self.assertNotEqual(get_cache_data, None)
- self.assertEqual(test_content, get_cache_data.content)
+ self.assertEqual(test_content.encode(), get_cache_data.content)
@override_settings(
@@ -1308,7 +1313,7 @@ class CacheI18nTest(TestCase):
# This is tightly coupled to the implementation,
# but it's the most straightforward way to test the key.
tz = force_text(timezone.get_current_timezone_name(), errors='ignore')
- tz = tz.encode('ascii', 'ignore').replace(' ', '_')
+ tz = tz.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
response = HttpResponse()
key = learn_cache_key(request, response)
self.assertIn(tz, key, "Cache keys should include the time zone name when time zones are active")
@@ -1320,7 +1325,7 @@ class CacheI18nTest(TestCase):
request = self._get_request()
lang = translation.get_language()
tz = force_text(timezone.get_current_timezone_name(), errors='ignore')
- tz = tz.encode('ascii', 'ignore').replace(' ', '_')
+ tz = tz.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
response = HttpResponse()
key = learn_cache_key(request, response)
self.assertNotIn(lang, key, "Cache keys shouldn't include the language name when i18n isn't active")
@@ -1373,7 +1378,7 @@ class CacheI18nTest(TestCase):
get_cache_data = FetchFromCacheMiddleware().process_request(request)
# cache must return content
self.assertNotEqual(get_cache_data, None)
- self.assertEqual(get_cache_data.content, content)
+ self.assertEqual(get_cache_data.content, content.encode())
# different QUERY_STRING, cache must be empty
request = self._get_request_cache(query_string='foo=bar&somethingelse=true')
get_cache_data = FetchFromCacheMiddleware().process_request(request)
@@ -1388,7 +1393,7 @@ class CacheI18nTest(TestCase):
get_cache_data = FetchFromCacheMiddleware().process_request(request)
# Check that we can recover the cache
self.assertNotEqual(get_cache_data, None)
- self.assertEqual(get_cache_data.content, en_message)
+ self.assertEqual(get_cache_data.content, en_message.encode())
# Check that we use etags
self.assertTrue(get_cache_data.has_header('ETag'))
# Check that we can disable etags
@@ -1404,11 +1409,11 @@ class CacheI18nTest(TestCase):
translation.activate('en')
# retrieve the content from cache
get_cache_data = FetchFromCacheMiddleware().process_request(request)
- self.assertEqual(get_cache_data.content, en_message)
+ self.assertEqual(get_cache_data.content, en_message.encode())
# change again the language
translation.activate('es')
get_cache_data = FetchFromCacheMiddleware().process_request(request)
- self.assertEqual(get_cache_data.content, es_message)
+ self.assertEqual(get_cache_data.content, es_message.encode())
# reset the language
translation.deactivate()
@@ -1513,8 +1518,8 @@ class CacheMiddlewareTest(TestCase):
# Repeating the request should result in a cache hit
result = middleware.process_request(request)
- self.assertNotEquals(result, None)
- self.assertEqual(result.content, 'Hello World 1')
+ self.assertNotEqual(result, None)
+ self.assertEqual(result.content, b'Hello World 1')
# The same request through a different middleware won't hit
result = prefix_middleware.process_request(request)
@@ -1522,8 +1527,8 @@ class CacheMiddlewareTest(TestCase):
# The same request with a timeout _will_ hit
result = timeout_middleware.process_request(request)
- self.assertNotEquals(result, None)
- self.assertEqual(result.content, 'Hello World 1')
+ self.assertNotEqual(result, None)
+ self.assertEqual(result.content, b'Hello World 1')
@override_settings(CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True)
def test_cache_middleware_anonymous_only_wont_cause_session_access(self):
@@ -1592,43 +1597,43 @@ class CacheMiddlewareTest(TestCase):
# Request the view once
response = default_view(request, '1')
- self.assertEqual(response.content, 'Hello World 1')
+ self.assertEqual(response.content, b'Hello World 1')
# Request again -- hit the cache
response = default_view(request, '2')
- self.assertEqual(response.content, 'Hello World 1')
+ self.assertEqual(response.content, b'Hello World 1')
# Requesting the same view with the explicit cache should yield the same result
response = explicit_default_view(request, '3')
- self.assertEqual(response.content, 'Hello World 1')
+ self.assertEqual(response.content, b'Hello World 1')
# Requesting with a prefix will hit a different cache key
response = explicit_default_with_prefix_view(request, '4')
- self.assertEqual(response.content, 'Hello World 4')
+ self.assertEqual(response.content, b'Hello World 4')
# Hitting the same view again gives a cache hit
response = explicit_default_with_prefix_view(request, '5')
- self.assertEqual(response.content, 'Hello World 4')
+ self.assertEqual(response.content, b'Hello World 4')
# And going back to the implicit cache will hit the same cache
response = default_with_prefix_view(request, '6')
- self.assertEqual(response.content, 'Hello World 4')
+ self.assertEqual(response.content, b'Hello World 4')
# Requesting from an alternate cache won't hit cache
response = other_view(request, '7')
- self.assertEqual(response.content, 'Hello World 7')
+ self.assertEqual(response.content, b'Hello World 7')
# But a repeated hit will hit cache
response = other_view(request, '8')
- self.assertEqual(response.content, 'Hello World 7')
+ self.assertEqual(response.content, b'Hello World 7')
# And prefixing the alternate cache yields yet another cache entry
response = other_with_prefix_view(request, '9')
- self.assertEqual(response.content, 'Hello World 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, 'Hello World 10')
+ self.assertEqual(response.content, b'Hello World 10')
# But if we wait a couple of seconds...
time.sleep(2)
@@ -1636,38 +1641,38 @@ class CacheMiddlewareTest(TestCase):
# ... the default cache will still hit
cache = get_cache('default')
response = default_view(request, '11')
- self.assertEqual(response.content, 'Hello World 1')
+ self.assertEqual(response.content, b'Hello World 1')
# ... the default cache with a prefix will still hit
response = default_with_prefix_view(request, '12')
- self.assertEqual(response.content, 'Hello World 4')
+ self.assertEqual(response.content, b'Hello World 4')
# ... the explicit default cache will still hit
response = explicit_default_view(request, '13')
- self.assertEqual(response.content, 'Hello World 1')
+ self.assertEqual(response.content, b'Hello World 1')
# ... the explicit default cache with a prefix will still hit
response = explicit_default_with_prefix_view(request, '14')
- self.assertEqual(response.content, 'Hello World 4')
+ self.assertEqual(response.content, b'Hello World 4')
# .. but a rapidly expiring cache won't hit
response = other_view(request, '15')
- self.assertEqual(response.content, 'Hello World 15')
+ self.assertEqual(response.content, b'Hello World 15')
# .. even if it has a prefix
response = other_with_prefix_view(request, '16')
- self.assertEqual(response.content, 'Hello World 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, 'Hello World 10')
+ self.assertEqual(response.content, b'Hello World 10')
# And if we wait a few more seconds
time.sleep(2)
- # the custom timeouot cache will miss
+ # the custom timeout cache will miss
response = other_with_timeout_view(request, '18')
- self.assertEqual(response.content, 'Hello World 18')
+ self.assertEqual(response.content, b'Hello World 18')
@override_settings(
diff --git a/tests/regressiontests/comment_tests/models.py b/tests/regressiontests/comment_tests/models.py
index 4093af14b5..472b66decd 100644
--- a/tests/regressiontests/comment_tests/models.py
+++ b/tests/regressiontests/comment_tests/models.py
@@ -3,9 +3,13 @@ Comments may be attached to any object. See the comment documentation for
more information.
"""
+from __future__ 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):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@@ -13,6 +17,7 @@ class Author(models.Model):
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Article(models.Model):
author = models.ForeignKey(Author)
headline = models.CharField(max_length=100)
@@ -20,6 +25,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
+@python_2_unicode_compatible
class Entry(models.Model):
title = models.CharField(max_length=250)
body = models.TextField()
diff --git a/tests/regressiontests/conditional_processing/models.py b/tests/regressiontests/conditional_processing/models.py
index d1a8ac605b..d0838e153d 100644
--- a/tests/regressiontests/conditional_processing/models.py
+++ b/tests/regressiontests/conditional_processing/models.py
@@ -1,4 +1,6 @@
# -*- coding:utf-8 -*-
+from __future__ import unicode_literals
+
from datetime import datetime
from django.test import TestCase
@@ -20,7 +22,7 @@ class ConditionalGet(TestCase):
def assertFullResponse(self, response, check_last_modified=True, check_etag=True):
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, FULL_RESPONSE)
+ self.assertEqual(response.content, FULL_RESPONSE.encode())
if check_last_modified:
self.assertEqual(response['Last-Modified'], LAST_MODIFIED_STR)
if check_etag:
@@ -28,7 +30,7 @@ class ConditionalGet(TestCase):
def assertNotModified(self, response):
self.assertEqual(response.status_code, 304)
- self.assertEqual(response.content, '')
+ self.assertEqual(response.content, b'')
def testWithoutConditions(self):
response = self.client.get('/condition/')
diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py
index 52fd3ea1c7..c5b66a31d1 100644
--- a/tests/regressiontests/csrf_tests/tests.py
+++ b/tests/regressiontests/csrf_tests/tests.py
@@ -216,7 +216,7 @@ class CsrfViewMiddlewareTest(TestCase):
"""
req = self._get_GET_no_csrf_cookie_request()
resp = token_view(req)
- self.assertEqual("", resp.content)
+ self.assertEqual(resp.content, b'')
def test_token_node_empty_csrf_cookie(self):
"""
diff --git a/tests/regressiontests/custom_columns_regress/models.py b/tests/regressiontests/custom_columns_regress/models.py
index c768c12772..169b2246c3 100644
--- a/tests/regressiontests/custom_columns_regress/models.py
+++ b/tests/regressiontests/custom_columns_regress/models.py
@@ -8,26 +8,29 @@ table creation or queries.
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):
Article_ID = models.AutoField(primary_key=True, db_column='Article ID')
headline = models.CharField(max_length=100)
authors = models.ManyToManyField('Author', db_table='my m2m table')
primary_author = models.ForeignKey('Author', db_column='Author ID', related_name='primary_set')
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
ordering = ('headline',)
+@python_2_unicode_compatible
class Author(models.Model):
Author_ID = models.AutoField(primary_key=True, db_column='Author ID')
first_name = models.CharField(max_length=30, db_column='first name')
last_name = models.CharField(max_length=30, db_column='last name')
- def __unicode__(self):
+ def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
class Meta:
diff --git a/tests/regressiontests/custom_managers_regress/models.py b/tests/regressiontests/custom_managers_regress/models.py
index 3c4e621769..71073f0fe7 100644
--- a/tests/regressiontests/custom_managers_regress/models.py
+++ b/tests/regressiontests/custom_managers_regress/models.py
@@ -3,6 +3,7 @@ Regression tests for custom manager classes.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class RestrictedManager(models.Manager):
@@ -12,12 +13,14 @@ class RestrictedManager(models.Manager):
def get_query_set(self):
return super(RestrictedManager, self).get_query_set().filter(is_public=True)
+@python_2_unicode_compatible
class RelatedModel(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class RestrictedModel(models.Model):
name = models.CharField(max_length=50)
is_public = models.BooleanField(default=False)
@@ -26,9 +29,10 @@ class RestrictedModel(models.Model):
objects = RestrictedManager()
plain_manager = models.Manager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class OneToOneRestrictedModel(models.Model):
name = models.CharField(max_length=50)
is_public = models.BooleanField(default=False)
@@ -37,5 +41,5 @@ class OneToOneRestrictedModel(models.Model):
objects = RestrictedManager()
plain_manager = models.Manager()
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/regressiontests/datatypes/models.py b/tests/regressiontests/datatypes/models.py
index 332ce84307..8e6027dd0f 100644
--- a/tests/regressiontests/datatypes/models.py
+++ b/tests/regressiontests/datatypes/models.py
@@ -4,8 +4,10 @@ types, which in the past were problematic for some database backends.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Donut(models.Model):
name = models.CharField(max_length=100)
is_frosted = models.BooleanField(default=False)
diff --git a/tests/regressiontests/dates/models.py b/tests/regressiontests/dates/models.py
index e1fc1e74fe..e4bffb7199 100644
--- a/tests/regressiontests/dates/models.py
+++ b/tests/regressiontests/dates/models.py
@@ -1,22 +1,25 @@
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.DateField()
categories = models.ManyToManyField("Category", related_name="articles")
- def __unicode__(self):
+ 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.DateField()
approval_date = models.DateField(null=True)
- def __unicode__(self):
+ def __str__(self):
return 'Comment to %s (%s)' % (self.article.title, self.pub_date)
class Category(models.Model):
diff --git a/tests/regressiontests/decorators/tests.py b/tests/regressiontests/decorators/tests.py
index dceaa62abc..7dc36aa479 100644
--- a/tests/regressiontests/decorators/tests.py
+++ b/tests/regressiontests/decorators/tests.py
@@ -220,7 +220,7 @@ class MethodDecoratorTests(TestCase):
self.assertEqual(getattr(Test.method, 'myattr2', False), True)
self.assertEqual(Test.method.__doc__, 'A method')
- self.assertEqual(Test.method.__func__.__name__, 'method')
+ self.assertEqual(Test.method.__name__, 'method')
class XFrameOptionsDecoratorsTests(TestCase):
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index 2852572d32..bdf3c867c6 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -9,6 +9,7 @@ from django.test import TestCase
from django.utils import six
from django.utils import unittest, translation
from django.utils.safestring import SafeData
+from django.utils.encoding import python_2_unicode_compatible
class DefaultFiltersTests(TestCase):
@@ -456,10 +457,11 @@ class DefaultFiltersTests(TestCase):
'Lawrence</li>\n\t\t\t<li>Topeka</li>\n\t\t</ul>\n\t\t</li>'\
'\n\t\t<li>Illinois</li>\n\t</ul>\n\t</li>')
+ @python_2_unicode_compatible
class ULItem(object):
def __init__(self, title):
self.title = title
- def __unicode__(self):
+ def __str__(self):
return 'ulitem-%s' % str(self.title)
a = ULItem('a')
diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py
index bd4f845f27..d02adccde9 100644
--- a/tests/regressiontests/defer_regress/models.py
+++ b/tests/regressiontests/defer_regress/models.py
@@ -3,15 +3,17 @@ Regression tests for defer() / only() behavior.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Item(models.Model):
name = models.CharField(max_length=15)
text = models.TextField(default="xyzzy")
value = models.IntegerField()
other_value = models.IntegerField(default=0)
- def __unicode__(self):
+ def __str__(self):
return self.name
class RelatedItem(models.Model):
@@ -21,13 +23,14 @@ class Child(models.Model):
name = models.CharField(max_length=10)
value = models.IntegerField()
+@python_2_unicode_compatible
class Leaf(models.Model):
name = models.CharField(max_length=10)
child = models.ForeignKey(Child)
second_child = models.ForeignKey(Child, related_name="other", null=True)
value = models.IntegerField(default=42)
- def __unicode__(self):
+ def __str__(self):
return self.name
class ResolveThis(models.Model):
@@ -38,11 +41,12 @@ class Proxy(Item):
class Meta:
proxy = True
+@python_2_unicode_compatible
class SimpleItem(models.Model):
name = models.CharField(max_length=15)
value = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.name
class Feature(models.Model):
diff --git a/tests/regressiontests/dispatch/tests/test_saferef.py b/tests/regressiontests/dispatch/tests/test_saferef.py
index cfe6c5df85..30eaddfe18 100644
--- a/tests/regressiontests/dispatch/tests/test_saferef.py
+++ b/tests/regressiontests/dispatch/tests/test_saferef.py
@@ -54,10 +54,8 @@ class SaferefTests(unittest.TestCase):
sd[s] = 1
for t in self.ts:
if hasattr(t, 'x'):
- self.assertTrue(sd.has_key(safeRef(t.x)))
self.assertTrue(safeRef(t.x) in sd)
else:
- self.assertTrue(sd.has_key(safeRef(t)))
self.assertTrue(safeRef(t) in sd)
def testRepresentation(self):
diff --git a/tests/regressiontests/expressions_regress/models.py b/tests/regressiontests/expressions_regress/models.py
index f3b6999377..711329031d 100644
--- a/tests/regressiontests/expressions_regress/models.py
+++ b/tests/regressiontests/expressions_regress/models.py
@@ -1,15 +1,17 @@
from __future__ import unicode_literals
+from django.utils.encoding import python_2_unicode_compatible
"""
Model for testing arithmetic expressions.
"""
from django.db import models
+@python_2_unicode_compatible
class Number(models.Model):
integer = models.IntegerField(db_column='the_integer')
float = models.FloatField(null=True, db_column='the_float')
- def __unicode__(self):
+ def __str__(self):
return '%i, %.3f' % (self.integer, self.float)
class Experiment(models.Model):
diff --git a/tests/regressiontests/extra_regress/models.py b/tests/regressiontests/extra_regress/models.py
index 7d2a6fab34..11996438b4 100644
--- a/tests/regressiontests/extra_regress/models.py
+++ b/tests/regressiontests/extra_regress/models.py
@@ -5,14 +5,16 @@ import datetime
from django.contrib.auth.models import User
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class RevisionableModel(models.Model):
base = models.ForeignKey('self', null=True)
title = models.CharField(blank=True, max_length=255)
when = models.DateTimeField(default=datetime.datetime.now)
- def __unicode__(self):
+ def __str__(self):
return "%s (%s, %s)" % (self.title, self.id, self.base.id)
def save(self, *args, **kwargs):
@@ -32,11 +34,12 @@ class Order(models.Model):
created_by = models.ForeignKey(User)
text = models.TextField()
+@python_2_unicode_compatible
class TestObject(models.Model):
first = models.CharField(max_length=20)
second = models.CharField(max_length=20)
third = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return 'TestObject: %s,%s,%s' % (self.first,self.second,self.third)
diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py
index 31e33d915c..87b509e320 100644
--- a/tests/regressiontests/file_storage/tests.py
+++ b/tests/regressiontests/file_storage/tests.py
@@ -21,6 +21,7 @@ from django.core.files.images import get_image_dimensions
from django.core.files.storage import FileSystemStorage, get_storage_class
from django.core.files.uploadedfile import UploadedFile
from django.test import SimpleTestCase
+from django.utils import six
from django.utils import unittest
from ..servers.tests import LiveServerBase
@@ -127,7 +128,7 @@ class FileStorageTests(unittest.TestCase):
"""
self.assertFalse(self.storage.exists('test.file'))
- f = ContentFile(b'custom contents')
+ f = ContentFile('custom contents')
f_name = self.storage.save('test.file', f)
atime = self.storage.accessed_time(f_name)
@@ -143,7 +144,7 @@ class FileStorageTests(unittest.TestCase):
"""
self.assertFalse(self.storage.exists('test.file'))
- f = ContentFile(b'custom contents')
+ f = ContentFile('custom contents')
f_name = self.storage.save('test.file', f)
ctime = self.storage.created_time(f_name)
@@ -160,7 +161,7 @@ class FileStorageTests(unittest.TestCase):
"""
self.assertFalse(self.storage.exists('test.file'))
- f = ContentFile(b'custom contents')
+ f = ContentFile('custom contents')
f_name = self.storage.save('test.file', f)
mtime = self.storage.modified_time(f_name)
@@ -177,7 +178,7 @@ class FileStorageTests(unittest.TestCase):
"""
self.assertFalse(self.storage.exists('test.file'))
- f = ContentFile(b'custom contents')
+ f = ContentFile('custom contents')
f.name = 'test.file'
storage_f_name = self.storage.save(None, f)
@@ -194,11 +195,11 @@ class FileStorageTests(unittest.TestCase):
"""
self.assertFalse(self.storage.exists('path/to'))
self.storage.save('path/to/test.file',
- ContentFile(b'file saved with path'))
+ ContentFile('file saved with path'))
self.assertTrue(self.storage.exists('path/to'))
- self.assertEqual(self.storage.open('path/to/test.file').read(),
- b'file saved with path')
+ with self.storage.open('path/to/test.file') as f:
+ self.assertEqual(f.read(), b'file saved with path')
self.assertTrue(os.path.exists(
os.path.join(self.temp_dir, 'path', 'to', 'test.file')))
@@ -211,7 +212,7 @@ class FileStorageTests(unittest.TestCase):
"""
self.assertFalse(self.storage.exists('test.file'))
- f = ContentFile(b'custom contents')
+ f = ContentFile('custom contents')
f_name = self.storage.save('test.file', f)
self.assertEqual(self.storage.path(f_name),
@@ -246,8 +247,8 @@ class FileStorageTests(unittest.TestCase):
self.assertFalse(self.storage.exists('storage_test_2'))
self.assertFalse(self.storage.exists('storage_dir_1'))
- f = self.storage.save('storage_test_1', ContentFile(b'custom content'))
- f = self.storage.save('storage_test_2', ContentFile(b'custom content'))
+ f = self.storage.save('storage_test_1', ContentFile('custom content'))
+ f = self.storage.save('storage_test_2', ContentFile('custom content'))
os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1'))
dirs, files = self.storage.listdir('')
@@ -304,18 +305,18 @@ class FileStorageTests(unittest.TestCase):
os.makedirs = fake_makedirs
self.storage.save('normal/test.file',
- ContentFile(b'saved normally'))
- self.assertEqual(self.storage.open('normal/test.file').read(),
- b'saved normally')
+ ContentFile('saved normally'))
+ with self.storage.open('normal/test.file') as f:
+ self.assertEqual(f.read(), b'saved normally')
self.storage.save('raced/test.file',
- ContentFile(b'saved with race'))
- self.assertEqual(self.storage.open('raced/test.file').read(),
- b'saved with race')
+ ContentFile('saved with race'))
+ with self.storage.open('raced/test.file') as f:
+ self.assertEqual(f.read(), b'saved with race')
# Check that OSErrors aside from EEXIST are still raised.
self.assertRaises(OSError,
- self.storage.save, 'error/test.file', ContentFile(b'not saved'))
+ self.storage.save, 'error/test.file', ContentFile('not saved'))
finally:
os.makedirs = real_makedirs
@@ -341,20 +342,31 @@ class FileStorageTests(unittest.TestCase):
try:
os.remove = fake_remove
- self.storage.save('normal.file', ContentFile(b'delete normally'))
+ self.storage.save('normal.file', ContentFile('delete normally'))
self.storage.delete('normal.file')
self.assertFalse(self.storage.exists('normal.file'))
- self.storage.save('raced.file', ContentFile(b'delete with race'))
+ self.storage.save('raced.file', ContentFile('delete with race'))
self.storage.delete('raced.file')
self.assertFalse(self.storage.exists('normal.file'))
# Check that OSErrors aside from ENOENT are still raised.
- self.storage.save('error.file', ContentFile(b'delete with error'))
+ self.storage.save('error.file', ContentFile('delete with error'))
self.assertRaises(OSError, self.storage.delete, 'error.file')
finally:
os.remove = real_remove
+ def test_file_chunks_error(self):
+ """
+ Test behaviour when file.chunks() is raising an error
+ """
+ f1 = ContentFile('chunks fails')
+ def failing_chunks():
+ raise IOError
+ f1.chunks = failing_chunks
+ with self.assertRaises(IOError):
+ self.storage.save('error.file', f1)
+
class CustomStorage(FileSystemStorage):
def get_available_name(self, name):
@@ -374,9 +386,9 @@ class CustomStorageTests(FileStorageTests):
storage_class = CustomStorage
def test_custom_get_available_name(self):
- first = self.storage.save('custom_storage', ContentFile(b'custom contents'))
+ first = self.storage.save('custom_storage', ContentFile('custom contents'))
self.assertEqual(first, 'custom_storage')
- second = self.storage.save('custom_storage', ContentFile(b'more contents'))
+ second = self.storage.save('custom_storage', ContentFile('more contents'))
self.assertEqual(second, 'custom_storage.2')
self.storage.delete(first)
self.storage.delete(second)
@@ -433,7 +445,7 @@ class FileStoragePermissions(unittest.TestCase):
shutil.rmtree(self.storage_dir)
def test_file_upload_permissions(self):
- name = self.storage.save("the_file", ContentFile(b"data"))
+ name = self.storage.save("the_file", ContentFile("data"))
actual_mode = os.stat(self.storage.path(name))[0] & 0o777
self.assertEqual(actual_mode, 0o666)
@@ -453,8 +465,8 @@ class FileStoragePathParsing(unittest.TestCase):
sure we still mangle the file name instead of the directory name.
"""
- self.storage.save('dotted.path/test', ContentFile(b"1"))
- self.storage.save('dotted.path/test', ContentFile(b"2"))
+ self.storage.save('dotted.path/test', ContentFile("1"))
+ self.storage.save('dotted.path/test', ContentFile("2"))
self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
@@ -465,8 +477,8 @@ class FileStoragePathParsing(unittest.TestCase):
File names with a dot as their first character don't have an extension,
and the underscore should get added to the end.
"""
- self.storage.save('dotted.path/.test', ContentFile(b"1"))
- self.storage.save('dotted.path/.test', ContentFile(b"2"))
+ self.storage.save('dotted.path/.test', ContentFile("1"))
+ self.storage.save('dotted.path/.test', ContentFile("2"))
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test')))
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
@@ -538,16 +550,25 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase):
self.assertEqual(size_1, size_2)
class ContentFileTestCase(unittest.TestCase):
- """
- Test that the constructor of ContentFile accepts 'name' (#16590).
- """
+
def test_content_file_default_name(self):
self.assertEqual(ContentFile(b"content").name, None)
def test_content_file_custom_name(self):
+ """
+ Test that the constructor of ContentFile accepts 'name' (#16590).
+ """
name = "I can have a name too!"
self.assertEqual(ContentFile(b"content", name=name).name, name)
+ def test_content_file_input_type(self):
+ """
+ Test that ContentFile can accept both bytes and unicode and that the
+ retrieved content is of the same type.
+ """
+ self.assertTrue(isinstance(ContentFile(b"content").read(), bytes))
+ self.assertTrue(isinstance(ContentFile("español").read(), six.text_type))
+
class NoNameFileTestCase(unittest.TestCase):
"""
Other examples of unnamed files may be tempfile.SpooledTemporaryFile or
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py
index d2362d7197..a545ed649e 100644
--- a/tests/regressiontests/file_uploads/tests.py
+++ b/tests/regressiontests/file_uploads/tests.py
@@ -12,6 +12,7 @@ from django.core.files import temp as tempfile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.http.multipartparser import MultiPartParser
from django.test import TestCase, client
+from django.utils.encoding import force_bytes
from django.utils.six import StringIO
from django.utils import unittest
@@ -48,12 +49,12 @@ class FileUploadTests(TestCase):
'file_field2': file2,
}
- for key in post_data.keys():
+ for key in list(post_data):
try:
post_data[key + '_hash'] = hashlib.sha1(post_data[key].read()).hexdigest()
post_data[key].seek(0)
except AttributeError:
- post_data[key + '_hash'] = hashlib.sha1(post_data[key]).hexdigest()
+ post_data[key + '_hash'] = hashlib.sha1(force_bytes(post_data[key])).hexdigest()
response = self.client.post('/file_uploads/verify/', post_data)
@@ -67,7 +68,7 @@ class FileUploadTests(TestCase):
'Content-Type: application/octet-stream',
'Content-Transfer-Encoding: base64',
'',
- base64.b64encode(test_string),
+ base64.b64encode(force_bytes(test_string)).decode('ascii'),
'--' + client.BOUNDARY + '--',
'',
]).encode('utf-8')
@@ -79,7 +80,7 @@ class FileUploadTests(TestCase):
'wsgi.input': client.FakePayload(payload),
}
response = self.client.request(**r)
- received = json.loads(response.content)
+ received = json.loads(response.content.decode('utf-8'))
self.assertEqual(received['file'], test_string)
@@ -87,7 +88,7 @@ class FileUploadTests(TestCase):
tdir = tempfile.gettempdir()
# This file contains chinese symbols and an accented char in the name.
- with open(os.path.join(tdir, UNICODE_FILENAME.encode('utf-8')), 'w+b') as file1:
+ with open(os.path.join(tdir, UNICODE_FILENAME), 'w+b') as file1:
file1.write(b'b' * (2 ** 10))
file1.seek(0)
@@ -150,7 +151,7 @@ class FileUploadTests(TestCase):
response = self.client.request(**r)
# The filenames should have been sanitized by the time it got to the view.
- recieved = json.loads(response.content)
+ recieved = json.loads(response.content.decode('utf-8'))
for i, name in enumerate(scary_file_names):
got = recieved["file%s" % i]
self.assertEqual(got, "hax0rd.txt")
@@ -174,7 +175,7 @@ class FileUploadTests(TestCase):
'REQUEST_METHOD': 'POST',
'wsgi.input': client.FakePayload(payload),
}
- got = json.loads(self.client.request(**r).content)
+ got = json.loads(self.client.request(**r).content.decode('utf-8'))
self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
def test_truncated_multipart_handled_gracefully(self):
@@ -200,7 +201,7 @@ class FileUploadTests(TestCase):
'REQUEST_METHOD': 'POST',
'wsgi.input': client.FakePayload(payload),
}
- got = json.loads(self.client.request(**r).content)
+ got = json.loads(self.client.request(**r).content.decode('utf-8'))
self.assertEqual(got, {})
def test_empty_multipart_handled_gracefully(self):
@@ -215,7 +216,7 @@ class FileUploadTests(TestCase):
'REQUEST_METHOD': 'POST',
'wsgi.input': client.FakePayload(b''),
}
- got = json.loads(self.client.request(**r).content)
+ got = json.loads(self.client.request(**r).content.decode('utf-8'))
self.assertEqual(got, {})
def test_custom_upload_handler(self):
@@ -231,12 +232,12 @@ class FileUploadTests(TestCase):
# Small file posting should work.
response = self.client.post('/file_uploads/quota/', {'f': smallfile})
- got = json.loads(response.content)
+ got = json.loads(response.content.decode('utf-8'))
self.assertTrue('f' in got)
# Large files don't go through.
response = self.client.post("/file_uploads/quota/", {'f': bigfile})
- got = json.loads(response.content)
+ got = json.loads(response.content.decode('utf-8'))
self.assertTrue('f' not in got)
def test_broken_custom_upload_handler(self):
@@ -274,7 +275,7 @@ class FileUploadTests(TestCase):
'field5': 'test7',
'file2': (file2, file2a)
})
- got = json.loads(response.content)
+ got = json.loads(response.content.decode('utf-8'))
self.assertEqual(got.get('file1'), 1)
self.assertEqual(got.get('file2'), 2)
@@ -299,8 +300,8 @@ class FileUploadTests(TestCase):
# it raises when there is an attempt to read more than the available bytes:
try:
client.FakePayload(b'a').read(2)
- except Exception as reference_error:
- pass
+ except Exception as err:
+ reference_error = err
# install the custom handler that tries to access request.POST
self.client.handler = POSTAccessingHandler()
diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py
index c5d2720e1a..fcf32cecea 100644
--- a/tests/regressiontests/file_uploads/views.py
+++ b/tests/regressiontests/file_uploads/views.py
@@ -7,6 +7,7 @@ import os
from django.core.files.uploadedfile import UploadedFile
from django.http import HttpResponse, HttpResponseServerError
from django.utils import six
+from django.utils.encoding import force_bytes
from .models import FileModel, UPLOAD_TO
from .tests import UNICODE_FILENAME
@@ -45,7 +46,7 @@ def file_upload_view_verify(request):
if isinstance(value, UploadedFile):
new_hash = hashlib.sha1(value.read()).hexdigest()
else:
- new_hash = hashlib.sha1(value).hexdigest()
+ new_hash = hashlib.sha1(force_bytes(value)).hexdigest()
if new_hash != submitted_hash:
return HttpResponseServerError()
@@ -95,7 +96,7 @@ def file_upload_echo_content(request):
"""
Simple view to echo back the content of uploaded files for tests.
"""
- r = dict([(k, f.read()) for k, f in request.FILES.items()])
+ r = dict([(k, f.read().decode('utf-8')) for k, f in request.FILES.items()])
return HttpResponse(json.dumps(r))
def file_upload_quota(request):
diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py
index 7151cb0ed9..1cc30d2e51 100644
--- a/tests/regressiontests/fixtures_regress/models.py
+++ b/tests/regressiontests/fixtures_regress/models.py
@@ -3,8 +3,10 @@ from __future__ import absolute_import, unicode_literals
from django.contrib.auth.models import User
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Animal(models.Model):
name = models.CharField(max_length=150)
latin_name = models.CharField(max_length=150)
@@ -14,7 +16,7 @@ class Animal(models.Model):
# use a non-default name for the default manager
specimens = models.Manager()
- def __unicode__(self):
+ def __str__(self):
return self.name
@@ -25,11 +27,12 @@ class Plant(models.Model):
# For testing when upper case letter in app name; regression for #4057
db_table = "Fixtures_regress_plant"
+@python_2_unicode_compatible
class Stuff(models.Model):
name = models.CharField(max_length=20, null=True)
owner = models.ForeignKey(User, null=True)
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.name) + ' is owned by ' + six.text_type(self.owner)
@@ -68,13 +71,14 @@ class Article(models.Model):
# Models to regression test #11428
+@python_2_unicode_compatible
class Widget(models.Model):
name = models.CharField(max_length=255)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
@@ -89,6 +93,7 @@ class TestManager(models.Manager):
return self.get(name=key)
+@python_2_unicode_compatible
class Store(models.Model):
objects = TestManager()
name = models.CharField(max_length=255)
@@ -97,13 +102,14 @@ class Store(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
def natural_key(self):
return (self.name,)
+@python_2_unicode_compatible
class Person(models.Model):
objects = TestManager()
name = models.CharField(max_length=255)
@@ -111,7 +117,7 @@ class Person(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
# Person doesn't actually have a dependency on store, but we need to define
@@ -121,6 +127,7 @@ class Person(models.Model):
natural_key.dependencies = ['fixtures_regress.store']
+@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField(max_length=255)
author = models.ForeignKey(Person)
@@ -129,7 +136,7 @@ class Book(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return '%s by %s (available at %s)' % (
self.name,
self.author.name,
@@ -142,6 +149,7 @@ class NKManager(models.Manager):
return self.get(data=data)
+@python_2_unicode_compatible
class NKChild(Parent):
data = models.CharField(max_length=10, unique=True)
objects = NKManager()
@@ -149,16 +157,17 @@ class NKChild(Parent):
def natural_key(self):
return self.data
- def __unicode__(self):
+ def __str__(self):
return 'NKChild %s:%s' % (self.name, self.data)
+@python_2_unicode_compatible
class RefToNKChild(models.Model):
text = models.CharField(max_length=10)
nk_fk = models.ForeignKey(NKChild, related_name='ref_fks')
nk_m2m = models.ManyToManyField(NKChild, related_name='ref_m2ms')
- def __unicode__(self):
+ def __str__(self):
return '%s: Reference to %s [%s]' % (
self.text,
self.nk_fk,
diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
index ab93341699..b3d0f42465 100644
--- a/tests/regressiontests/fixtures_regress/tests.py
+++ b/tests/regressiontests/fixtures_regress/tests.py
@@ -4,7 +4,6 @@ from __future__ import absolute_import, unicode_literals
import os
import re
-from io import BytesIO
from django.core import management
from django.core.management.base import CommandError
@@ -14,6 +13,7 @@ from django.db.models import signals
from django.test import (TestCase, TransactionTestCase, skipIfDBFeature,
skipUnlessDBFeature)
from django.test.utils import override_settings
+from django.utils.six import PY3, StringIO
from .models import (Animal, Stuff, Absolute, Parent, Child, Article, Widget,
Store, Person, Book, NKChild, RefToNKChild, Circle1, Circle2, Circle3,
@@ -126,6 +126,20 @@ class TestFixtures(TestCase):
commit=False,
)
+ @override_settings(SERIALIZATION_MODULES={'unkn': 'unexistent.path'})
+ def test_unimportable_serializer(self):
+ """
+ Test that failing serializer import raises the proper error
+ """
+ with self.assertRaisesRegexp(ImportError,
+ "No module named unexistent.path"):
+ management.call_command(
+ 'loaddata',
+ 'bad_fixture1.unkn',
+ verbosity=0,
+ commit=False,
+ )
+
def test_invalid_data(self):
"""
Test for ticket #4371 -- Loading a fixture file with invalid data
@@ -244,7 +258,8 @@ class TestFixtures(TestCase):
self.assertEqual(
pre_save_checks,
[
- ("Count = 42 (<type 'int'>)", "Weight = 1.2 (<type 'float'>)")
+ ("Count = 42 (<%s 'int'>)" % ('class' if PY3 else 'type'),
+ "Weight = 1.2 (<%s 'float'>)" % ('class' if PY3 else 'type'))
]
)
finally:
@@ -276,7 +291,7 @@ class TestFixtures(TestCase):
)
animal.save()
- stdout = BytesIO()
+ stdout = StringIO()
management.call_command(
'dumpdata',
'fixtures_regress.animal',
@@ -305,7 +320,7 @@ class TestFixtures(TestCase):
"""
Regression for #11428 - Proxy models aren't included when you dumpdata
"""
- stdout = BytesIO()
+ stdout = StringIO()
# Create an instance of the concrete class
widget = Widget.objects.create(name='grommet')
management.call_command(
@@ -380,7 +395,7 @@ class TestFixtures(TestCase):
)
def test_loaddata_not_existant_fixture_file(self):
- stdout_output = BytesIO()
+ stdout_output = StringIO()
management.call_command(
'loaddata',
'this_fixture_doesnt_exist',
@@ -465,7 +480,7 @@ class NaturalKeyFixtureTests(TestCase):
commit=False
)
- stdout = BytesIO()
+ stdout = StringIO()
management.call_command(
'dumpdata',
'fixtures_regress.book',
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
index 18e6ddce6d..2f3ee9fa31 100644
--- a/tests/regressiontests/forms/models.py
+++ b/tests/regressiontests/forms/models.py
@@ -7,6 +7,7 @@ import tempfile
from django.core.files.storage import FileSystemStorage
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
temp_storage_location = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR'])
@@ -36,6 +37,7 @@ class ChoiceModel(models.Model):
name = models.CharField(max_length=10)
+@python_2_unicode_compatible
class ChoiceOptionModel(models.Model):
"""Destination for ChoiceFieldModel's ForeignKey.
Can't reuse ChoiceModel because error_message tests require that it have no instances."""
@@ -44,7 +46,7 @@ class ChoiceOptionModel(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return 'ChoiceOption %d' % self.pk
@@ -66,10 +68,11 @@ class FileModel(models.Model):
file = models.FileField(storage=temp_storage, upload_to='tests')
+@python_2_unicode_compatible
class Group(models.Model):
name = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return '%s' % self.name
diff --git a/tests/regressiontests/forms/tests/error_messages.py b/tests/regressiontests/forms/tests/error_messages.py
index f69b419483..b76e122bc0 100644
--- a/tests/regressiontests/forms/tests/error_messages.py
+++ b/tests/regressiontests/forms/tests/error_messages.py
@@ -5,6 +5,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.forms import *
from django.test import TestCase
from django.utils.safestring import mark_safe
+from django.utils.encoding import python_2_unicode_compatible
class AssertFormErrorsMixin(object):
@@ -213,8 +214,9 @@ class FormsErrorMessagesTestCase(TestCase, AssertFormErrorsMixin):
def clean(self):
raise ValidationError("I like to be awkward.")
+ @python_2_unicode_compatible
class CustomErrorList(util.ErrorList):
- def __unicode__(self):
+ def __str__(self):
return self.as_divs()
def as_divs(self):
diff --git a/tests/regressiontests/forms/tests/extra.py b/tests/regressiontests/forms/tests/extra.py
index e0e62858d0..2ab5d40942 100644
--- a/tests/regressiontests/forms/tests/extra.py
+++ b/tests/regressiontests/forms/tests/extra.py
@@ -8,8 +8,9 @@ from django.forms import *
from django.forms.extras import SelectDateWidget
from django.forms.util import ErrorList
from django.test import TestCase
+from django.utils import six
from django.utils import translation
-from django.utils.encoding import force_text, smart_text
+from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
from .error_messages import AssertFormErrorsMixin
@@ -553,14 +554,24 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
def test_smart_text(self):
class Test:
- def __str__(self):
- return 'ŠĐĆŽćžšđ'.encode('utf-8')
+ if six.PY3:
+ def __str__(self):
+ return 'ŠĐĆŽćžšđ'
+ else:
+ def __str__(self):
+ return 'ŠĐĆŽćžšđ'.encode('utf-8')
class TestU:
- def __str__(self):
- return 'Foo'
- def __unicode__(self):
- return '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
+ if six.PY3:
+ def __str__(self):
+ return 'ŠĐĆŽćžšđ'
+ def __bytes__(self):
+ return b'Foo'
+ else:
+ def __str__(self):
+ return b'Foo'
+ def __unicode__(self):
+ return '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
self.assertEqual(smart_text(Test()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
self.assertEqual(smart_text(TestU()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
@@ -585,8 +596,9 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
self.assertEqual(f.cleaned_data['username'], 'sirrobin')
def test_overriding_errorlist(self):
+ @python_2_unicode_compatible
class DivErrorList(ErrorList):
- def __unicode__(self):
+ def __str__(self):
return self.as_divs()
def as_divs(self):
diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py
index feb2ade458..6efdb9682f 100644
--- a/tests/regressiontests/forms/tests/fields.py
+++ b/tests/regressiontests/forms/tests/fields.py
@@ -668,6 +668,18 @@ class FieldsTests(SimpleTestCase):
# Valid IDN
self.assertEqual(url, f.clean(url))
+ def test_urlfield_10(self):
+ """Test URLField correctly validates IPv6 (#18779)."""
+ f = URLField()
+ urls = (
+ 'http://::/',
+ 'http://6:21b4:92/',
+ 'http://[12:34:3a53]/',
+ 'http://[a34:9238::]:8080/',
+ )
+ for url in urls:
+ self.assertEqual(url, f.clean(url))
+
def test_urlfield_not_string(self):
f = URLField(required=False)
self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'", f.clean, 23)
diff --git a/tests/regressiontests/forms/tests/models.py b/tests/regressiontests/forms/tests/models.py
index 7687335b48..c351509cee 100644
--- a/tests/regressiontests/forms/tests/models.py
+++ b/tests/regressiontests/forms/tests/models.py
@@ -178,7 +178,7 @@ class RelatedModelFormTests(TestCase):
class Meta:
model=A
- self.assertRaises(ValueError, ModelFormMetaclass, b'Form', (ModelForm,), {'Meta': Meta})
+ self.assertRaises(ValueError, ModelFormMetaclass, str('Form'), (ModelForm,), {'Meta': Meta})
class B(models.Model):
pass
@@ -196,4 +196,4 @@ class RelatedModelFormTests(TestCase):
class Meta:
model=A
- self.assertTrue(issubclass(ModelFormMetaclass(b'Form', (ModelForm,), {'Meta': Meta}), ModelForm))
+ self.assertTrue(issubclass(ModelFormMetaclass(str('Form'), (ModelForm,), {'Meta': Meta}), ModelForm))
diff --git a/tests/regressiontests/forms/tests/util.py b/tests/regressiontests/forms/tests/util.py
index b7cc4ec809..c2d213ae23 100644
--- a/tests/regressiontests/forms/tests/util.py
+++ b/tests/regressiontests/forms/tests/util.py
@@ -7,6 +7,7 @@ from django.test import TestCase
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.translation import ugettext_lazy
+from django.utils.encoding import python_2_unicode_compatible
class FormsUtilTestCase(TestCase):
@@ -46,8 +47,9 @@ class FormsUtilTestCase(TestCase):
self.assertHTMLEqual(str(ErrorList(ValidationError(["First error.", "Not \u03C0.", ugettext_lazy("Error.")]).messages)),
'<ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>')
+ @python_2_unicode_compatible
class VeryBadError:
- def __unicode__(self): return "A very bad error."
+ def __str__(self): return "A very bad error."
# Can take a non-string.
self.assertHTMLEqual(str(ErrorList(ValidationError(VeryBadError()).messages)),
diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py
index 3ea42cf549..c950aef719 100644
--- a/tests/regressiontests/forms/tests/widgets.py
+++ b/tests/regressiontests/forms/tests/widgets.py
@@ -13,6 +13,7 @@ from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.translation import activate, deactivate
from django.test import TestCase
+from django.utils.encoding import python_2_unicode_compatible
class FormsWidgetTestCase(TestCase):
@@ -1096,6 +1097,7 @@ class WidgetTests(TestCase):
self.assertFalse(form.is_valid())
+@python_2_unicode_compatible
class FakeFieldFile(object):
"""
Quacks like a FieldFile (has a .url and unicode representation), but
@@ -1104,7 +1106,7 @@ class FakeFieldFile(object):
"""
url = 'something'
- def __unicode__(self):
+ def __str__(self):
return self.url
class ClearableFileInputTests(TestCase):
@@ -1125,10 +1127,11 @@ class ClearableFileInputTests(TestCase):
rendering HTML. Refs #15182.
"""
+ @python_2_unicode_compatible
class StrangeFieldFile(object):
url = "something?chapter=1&sect=2&copy=3&lang=en"
- def __unicode__(self):
+ def __str__(self):
return '''something<div onclick="alert('oops')">.jpg'''
widget = ClearableFileInput()
diff --git a/tests/regressiontests/generic_inline_admin/models.py b/tests/regressiontests/generic_inline_admin/models.py
index a1555666b3..dedc351075 100644
--- a/tests/regressiontests/generic_inline_admin/models.py
+++ b/tests/regressiontests/generic_inline_admin/models.py
@@ -1,6 +1,7 @@
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
class Episode(models.Model):
@@ -9,6 +10,7 @@ class Episode(models.Model):
author = models.CharField(max_length=100, blank=True)
+@python_2_unicode_compatible
class Media(models.Model):
"""
Media that can associated to any object.
@@ -20,7 +22,7 @@ class Media(models.Model):
description = models.CharField(max_length=100, blank=True)
keywords = models.CharField(max_length=100, blank=True)
- def __unicode__(self):
+ def __str__(self):
return self.url
#
diff --git a/tests/regressiontests/generic_inline_admin/tests.py b/tests/regressiontests/generic_inline_admin/tests.py
index c97f4c4f03..fea30b4946 100644
--- a/tests/regressiontests/generic_inline_admin/tests.py
+++ b/tests/regressiontests/generic_inline_admin/tests.py
@@ -276,7 +276,7 @@ class GenericInlineModelAdminTest(TestCase):
ma = EpisodeAdmin(Episode, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
['keywords', 'id', 'DELETE'])
def test_custom_form_meta_exclude(self):
@@ -306,7 +306,7 @@ class GenericInlineModelAdminTest(TestCase):
ma = EpisodeAdmin(Episode, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
['url', 'keywords', 'id', 'DELETE'])
# Then, only with `ModelForm` -----------------
@@ -322,5 +322,5 @@ class GenericInlineModelAdminTest(TestCase):
ma = EpisodeAdmin(Episode, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
['description', 'keywords', 'id', 'DELETE'])
diff --git a/tests/regressiontests/generic_relations_regress/models.py b/tests/regressiontests/generic_relations_regress/models.py
index 7ec752b02b..dacc9b380b 100644
--- a/tests/regressiontests/generic_relations_regress/models.py
+++ b/tests/regressiontests/generic_relations_regress/models.py
@@ -1,31 +1,36 @@
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
__all__ = ('Link', 'Place', 'Restaurant', 'Person', 'Address',
'CharLink', 'TextLink', 'OddRelation1', 'OddRelation2',
'Contact', 'Organization', 'Note')
+@python_2_unicode_compatible
class Link(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
- def __unicode__(self):
+ def __str__(self):
return "Link to %s id=%s" % (self.content_type, self.object_id)
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=100)
links = generic.GenericRelation(Link)
- def __unicode__(self):
+ def __str__(self):
return "Place: %s" % self.name
+@python_2_unicode_compatible
class Restaurant(Place):
- def __unicode__(self):
+ def __str__(self):
return "Restaurant: %s" % self.name
+@python_2_unicode_compatible
class Address(models.Model):
street = models.CharField(max_length=80)
city = models.CharField(max_length=50)
@@ -35,15 +40,16 @@ class Address(models.Model):
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
- def __unicode__(self):
+ def __str__(self):
return '%s %s, %s %s' % (self.street, self.city, self.state, self.zipcode)
+@python_2_unicode_compatible
class Person(models.Model):
account = models.IntegerField(primary_key=True)
name = models.CharField(max_length=128)
addresses = generic.GenericRelation(Address)
- def __unicode__(self):
+ def __str__(self):
return self.name
class CharLink(models.Model):
diff --git a/tests/regressiontests/generic_views/base.py b/tests/regressiontests/generic_views/base.py
index fc3dc05fd7..439b0d7327 100644
--- a/tests/regressiontests/generic_views/base.py
+++ b/tests/regressiontests/generic_views/base.py
@@ -69,7 +69,7 @@ class ViewTest(unittest.TestCase):
def _assert_simple(self, response):
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'This is a simple view')
+ self.assertEqual(response.content, b'This is a simple view')
def test_no_init_kwargs(self):
"""
@@ -173,15 +173,6 @@ class ViewTest(unittest.TestCase):
"""
self.assertTrue(DecoratedDispatchView.as_view().is_decorated)
- def test_head_no_get(self):
- """
- Test that a view class with no get responds to a HEAD request with HTTP
- 405.
- """
- request = self.rf.head('/')
- view = PostOnlyView.as_view()
- self.assertEqual(405, view(request).status_code)
-
def test_options(self):
"""
Test that views respond to HTTP OPTIONS requests with an Allow header
@@ -275,7 +266,8 @@ class TemplateViewTest(TestCase):
"""
response = self.client.get('/template/simple/bar/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['params'], {'foo': 'bar'})
+ self.assertEqual(response.context['foo'], 'bar')
+ self.assertTrue(isinstance(response.context['view'], View))
def test_extra_template_params(self):
"""
@@ -283,8 +275,9 @@ class TemplateViewTest(TestCase):
"""
response = self.client.get('/template/custom/bar/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['params'], {'foo': 'bar'})
+ self.assertEqual(response.context['foo'], 'bar')
self.assertEqual(response.context['key'], 'value')
+ self.assertTrue(isinstance(response.context['view'], View))
def test_cached_views(self):
"""
diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py
index ad65fd9f10..c2fa71b376 100644
--- a/tests/regressiontests/generic_views/dates.py
+++ b/tests/regressiontests/generic_views/dates.py
@@ -1,14 +1,27 @@
from __future__ import absolute_import
+import time
import datetime
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import timezone
+from django.utils.unittest import skipUnless
from .models import Book, BookSigning
+TZ_SUPPORT = hasattr(time, 'tzset')
+
+# On OSes that don't provide tzset (Windows), we can't set the timezone
+# in which the program runs. As a consequence, we must skip tests that
+# don't enforce a specific timezone (with timezone.override or equivalent),
+# or attempt to interpret naive datetimes in the default timezone.
+
+requires_tz_support = skipUnless(TZ_SUPPORT,
+ "This test relies on the ability to run a program in an arbitrary "
+ "time zone, but your operating system isn't able to do that.")
+
class ArchiveIndexViewTests(TestCase):
fixtures = ['generic-views-test-data.json']
@@ -47,7 +60,6 @@ class ArchiveIndexViewTests(TestCase):
res = self.client.get('/dates/books/allow_empty/')
self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['date_list']), [])
- self.assertEqual(list(res.context['date_list']), [])
self.assertTemplateUsed(res, 'generic_views/book_archive.html')
def test_archive_view_template(self):
@@ -67,6 +79,11 @@ class ArchiveIndexViewTests(TestCase):
def test_archive_view_invalid(self):
self.assertRaises(ImproperlyConfigured, self.client.get, '/dates/books/invalid/')
+ def test_archive_view_by_month(self):
+ res = self.client.get('/dates/books/by_month/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'month')[::-1])
+
def test_paginated_archive_view(self):
self._make_books(20, base_date=datetime.date.today())
res = self.client.get('/dates/books/paginated/')
@@ -100,6 +117,7 @@ class ArchiveIndexViewTests(TestCase):
res = self.client.get('/dates/booksignings/')
self.assertEqual(res.status_code, 200)
+ @requires_tz_support
@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))
@@ -480,7 +498,7 @@ class DayArchiveViewTests(TestCase):
def test_next_prev_context(self):
res = self.client.get('/dates/books/2008/oct/01/')
- self.assertEqual(res.content, "Archive for Oct. 1, 2008. Previous day is May 1, 2006")
+ self.assertEqual(res.content, b"Archive for Oct. 1, 2008. Previous day is May 1, 2006")
def test_custom_month_format(self):
res = self.client.get('/dates/books/2008/10/01/')
@@ -502,6 +520,7 @@ class DayArchiveViewTests(TestCase):
res = self.client.get('/dates/booksignings/2008/apr/2/')
self.assertEqual(res.status_code, 200)
+ @requires_tz_support
@override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
def test_aware_datetime_day_view(self):
bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
@@ -578,6 +597,7 @@ class DateDetailViewTests(TestCase):
res = self.client.get('/dates/booksignings/2008/apr/2/%d/' % bs.pk)
self.assertEqual(res.status_code, 200)
+ @requires_tz_support
@override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
def test_aware_datetime_date_detail(self):
bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
diff --git a/tests/regressiontests/generic_views/detail.py b/tests/regressiontests/generic_views/detail.py
index 60c5f7afe9..c8f1999a2a 100644
--- a/tests/regressiontests/generic_views/detail.py
+++ b/tests/regressiontests/generic_views/detail.py
@@ -2,6 +2,7 @@ from __future__ import absolute_import
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
+from django.views.generic.base import View
from .models import Artist, Author, Page
@@ -14,6 +15,7 @@ class DetailViewTest(TestCase):
res = self.client.get('/detail/obj/')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context['object'], {'foo': 'bar'})
+ self.assertTrue(isinstance(res.context['view'], View))
self.assertTemplateUsed(res, 'generic_views/detail.html')
def test_detail_by_pk(self):
diff --git a/tests/regressiontests/generic_views/edit.py b/tests/regressiontests/generic_views/edit.py
index 651e14fff3..16f4da8efe 100644
--- a/tests/regressiontests/generic_views/edit.py
+++ b/tests/regressiontests/generic_views/edit.py
@@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse
from django import forms
from django.test import TestCase
from django.utils.unittest import expectedFailure
+from django.views.generic.base import View
from django.views.generic.edit import FormMixin
from . import views
@@ -31,6 +32,7 @@ class CreateViewTests(TestCase):
res = self.client.get('/edit/authors/create/')
self.assertEqual(res.status_code, 200)
self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
+ self.assertTrue(isinstance(res.context['view'], View))
self.assertFalse('object' in res.context)
self.assertFalse('author' in res.context)
self.assertTemplateUsed(res, 'generic_views/author_form.html')
@@ -101,6 +103,7 @@ class CreateViewTests(TestCase):
self.assertEqual(res.status_code, 302)
self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/')
+
class UpdateViewTests(TestCase):
urls = 'regressiontests.generic_views.urls'
@@ -226,6 +229,7 @@ class UpdateViewTests(TestCase):
res = self.client.get('/edit/author/update/')
self.assertEqual(res.status_code, 200)
self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
+ self.assertTrue(isinstance(res.context['view'], View))
self.assertEqual(res.context['object'], Author.objects.get(pk=a.pk))
self.assertEqual(res.context['author'], Author.objects.get(pk=a.pk))
self.assertTemplateUsed(res, 'generic_views/author_form.html')
@@ -237,6 +241,7 @@ class UpdateViewTests(TestCase):
self.assertRedirects(res, 'http://testserver/list/authors/')
self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (xkcd)>'])
+
class DeleteViewTests(TestCase):
urls = 'regressiontests.generic_views.urls'
diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py
index b925758524..14dc1d725d 100644
--- a/tests/regressiontests/generic_views/list.py
+++ b/tests/regressiontests/generic_views/list.py
@@ -2,6 +2,7 @@ from __future__ import absolute_import
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
+from django.views.generic.base import View
from .models import Author, Artist
@@ -21,6 +22,7 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertTrue(isinstance(res.context['view'], View))
self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertIsNone(res.context['paginator'])
self.assertIsNone(res.context['page_obj'])
diff --git a/tests/regressiontests/generic_views/models.py b/tests/regressiontests/generic_views/models.py
index 2355769d58..f59389ef78 100644
--- a/tests/regressiontests/generic_views/models.py
+++ b/tests/regressiontests/generic_views/models.py
@@ -1,6 +1,8 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Artist(models.Model):
name = models.CharField(max_length=100)
@@ -9,13 +11,14 @@ class Artist(models.Model):
verbose_name = 'professional artist'
verbose_name_plural = 'professional artists'
- def __unicode__(self):
+ def __str__(self):
return self.name
@models.permalink
def get_absolute_url(self):
return ('artist_detail', (), {'pk': self.id})
+@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField()
@@ -23,9 +26,10 @@ class Author(models.Model):
class Meta:
ordering = ['name']
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField(max_length=300)
slug = models.SlugField()
@@ -36,7 +40,7 @@ class Book(models.Model):
class Meta:
ordering = ['-pubdate']
- def __unicode__(self):
+ def __str__(self):
return self.name
class Page(models.Model):
diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py
index 5e15c6c9c1..c72bfecb65 100644
--- a/tests/regressiontests/generic_views/urls.py
+++ b/tests/regressiontests/generic_views/urls.py
@@ -113,6 +113,8 @@ urlpatterns = patterns('',
views.BookArchive.as_view(paginate_by=10)),
(r'^dates/books/reverse/$',
views.BookArchive.as_view(queryset=models.Book.objects.order_by('pubdate'))),
+ (r'^dates/books/by_month/$',
+ views.BookArchive.as_view(date_list_period='month')),
(r'^dates/booksignings/$',
views.BookSigningArchive.as_view()),
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index 0f61c2d840..21ba198bc3 100644
--- a/tests/regressiontests/httpwrappers/tests.py
+++ b/tests/regressiontests/httpwrappers/tests.py
@@ -6,19 +6,22 @@ import pickle
from django.core.exceptions import SuspiciousOperation
from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
- HttpResponsePermanentRedirect,
+ HttpResponsePermanentRedirect, HttpResponseNotAllowed,
+ HttpResponseNotModified,
SimpleCookie, BadHeaderError,
parse_cookie)
+from django.test import TestCase
+from django.utils import six
from django.utils import unittest
class QueryDictTests(unittest.TestCase):
def test_missing_key(self):
- q = QueryDict('')
+ q = QueryDict(str(''))
self.assertRaises(KeyError, q.__getitem__, 'foo')
def test_immutability(self):
- q = QueryDict('')
+ q = QueryDict(str(''))
self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
@@ -28,26 +31,26 @@ class QueryDictTests(unittest.TestCase):
self.assertRaises(AttributeError, q.clear)
def test_immutable_get_with_default(self):
- q = QueryDict('')
+ q = QueryDict(str(''))
self.assertEqual(q.get('foo', 'default'), 'default')
def test_immutable_basic_operations(self):
- q = QueryDict('')
+ q = QueryDict(str(''))
self.assertEqual(q.getlist('foo'), [])
- self.assertEqual(q.has_key('foo'), False)
+ if not six.PY3:
+ self.assertEqual(q.has_key('foo'), False)
self.assertEqual('foo' in q, False)
- self.assertEqual(q.items(), [])
- self.assertEqual(q.lists(), [])
- self.assertEqual(q.items(), [])
- self.assertEqual(q.keys(), [])
- self.assertEqual(q.values(), [])
+ self.assertEqual(list(six.iteritems(q)), [])
+ self.assertEqual(list(six.iterlists(q)), [])
+ self.assertEqual(list(six.iterkeys(q)), [])
+ self.assertEqual(list(six.itervalues(q)), [])
self.assertEqual(len(q), 0)
self.assertEqual(q.urlencode(), '')
def test_single_key_value(self):
"""Test QueryDict with one key/value pair"""
- q = QueryDict('foo=bar')
+ q = QueryDict(str('foo=bar'))
self.assertEqual(q['foo'], 'bar')
self.assertRaises(KeyError, q.__getitem__, 'bar')
self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
@@ -60,15 +63,17 @@ class QueryDictTests(unittest.TestCase):
self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
- self.assertTrue(q.has_key('foo'))
+ if not six.PY3:
+ self.assertTrue(q.has_key('foo'))
self.assertTrue('foo' in q)
- self.assertFalse(q.has_key('bar'))
+ if not six.PY3:
+ self.assertFalse(q.has_key('bar'))
self.assertFalse('bar' in q)
- self.assertEqual(q.items(), [('foo', 'bar')])
- self.assertEqual(q.lists(), [('foo', ['bar'])])
- self.assertEqual(q.keys(), ['foo'])
- self.assertEqual(q.values(), ['bar'])
+ self.assertEqual(list(six.iteritems(q)), [('foo', 'bar')])
+ self.assertEqual(list(six.iterlists(q)), [('foo', ['bar'])])
+ self.assertEqual(list(six.iterkeys(q)), ['foo'])
+ self.assertEqual(list(six.itervalues(q)), ['bar'])
self.assertEqual(len(q), 1)
self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
@@ -80,30 +85,30 @@ class QueryDictTests(unittest.TestCase):
self.assertEqual(q.urlencode(), 'foo=bar')
def test_urlencode(self):
- q = QueryDict('', mutable=True)
+ q = QueryDict(str(''), mutable=True)
q['next'] = '/a&b/'
self.assertEqual(q.urlencode(), 'next=%2Fa%26b%2F')
self.assertEqual(q.urlencode(safe='/'), 'next=/a%26b/')
- q = QueryDict('', mutable=True)
+ q = QueryDict(str(''), mutable=True)
q['next'] = '/t\xebst&key/'
self.assertEqual(q.urlencode(), 'next=%2Ft%C3%ABst%26key%2F')
self.assertEqual(q.urlencode(safe='/'), 'next=/t%C3%ABst%26key/')
def test_mutable_copy(self):
"""A copy of a QueryDict is mutable."""
- q = QueryDict('').copy()
+ q = QueryDict(str('')).copy()
self.assertRaises(KeyError, q.__getitem__, "foo")
q['name'] = 'john'
self.assertEqual(q['name'], 'john')
def test_mutable_delete(self):
- q = QueryDict('').copy()
+ q = QueryDict(str('')).copy()
q['name'] = 'john'
del q['name']
self.assertFalse('name' in q)
def test_basic_mutable_operations(self):
- q = QueryDict('').copy()
+ q = QueryDict(str('')).copy()
q['name'] = 'john'
self.assertEqual(q.get('foo', 'default'), 'default')
self.assertEqual(q.get('name', 'default'), 'john')
@@ -117,13 +122,14 @@ class QueryDictTests(unittest.TestCase):
q.appendlist('foo', 'another')
self.assertEqual(q.getlist('foo'), ['bar', 'baz', 'another'])
self.assertEqual(q['foo'], 'another')
- self.assertTrue(q.has_key('foo'))
+ if not six.PY3:
+ self.assertTrue(q.has_key('foo'))
self.assertTrue('foo' in q)
- self.assertEqual(q.items(), [('foo', 'another'), ('name', 'john')])
- self.assertEqual(q.lists(), [('foo', ['bar', 'baz', 'another']), ('name', ['john'])])
- self.assertEqual(q.keys(), ['foo', 'name'])
- self.assertEqual(q.values(), ['another', 'john'])
+ self.assertEqual(list(six.iteritems(q)), [('foo', 'another'), ('name', 'john')])
+ self.assertEqual(list(six.iterlists(q)), [('foo', ['bar', 'baz', 'another']), ('name', ['john'])])
+ self.assertEqual(list(six.iterkeys(q)), ['foo', 'name'])
+ self.assertEqual(list(six.itervalues(q)), ['another', 'john'])
self.assertEqual(len(q), 2)
q.update({'foo': 'hello'})
@@ -144,7 +150,7 @@ class QueryDictTests(unittest.TestCase):
def test_multiple_keys(self):
"""Test QueryDict with two key/value pairs with same keys."""
- q = QueryDict('vote=yes&vote=no')
+ q = QueryDict(str('vote=yes&vote=no'))
self.assertEqual(q['vote'], 'no')
self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
@@ -158,14 +164,16 @@ class QueryDictTests(unittest.TestCase):
self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
- self.assertEqual(q.has_key('vote'), True)
+ if not six.PY3:
+ self.assertEqual(q.has_key('vote'), True)
self.assertEqual('vote' in q, True)
- self.assertEqual(q.has_key('foo'), False)
+ if not six.PY3:
+ self.assertEqual(q.has_key('foo'), False)
self.assertEqual('foo' in q, False)
- self.assertEqual(q.items(), [('vote', 'no')])
- self.assertEqual(q.lists(), [('vote', ['yes', 'no'])])
- self.assertEqual(q.keys(), ['vote'])
- self.assertEqual(q.values(), ['no'])
+ self.assertEqual(list(six.iteritems(q)), [('vote', 'no')])
+ self.assertEqual(list(six.iterlists(q)), [('vote', ['yes', 'no'])])
+ self.assertEqual(list(six.iterkeys(q)), ['vote'])
+ self.assertEqual(list(six.itervalues(q)), ['no'])
self.assertEqual(len(q), 1)
self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
@@ -175,45 +183,49 @@ class QueryDictTests(unittest.TestCase):
self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
self.assertRaises(AttributeError, q.__delitem__, 'vote')
- def test_invalid_input_encoding(self):
- """
- QueryDicts must be able to handle invalid input encoding (in this
- case, bad UTF-8 encoding).
- """
- q = QueryDict(b'foo=bar&foo=\xff')
- self.assertEqual(q['foo'], '\ufffd')
- self.assertEqual(q.getlist('foo'), ['bar', '\ufffd'])
+ if not six.PY3:
+ def test_invalid_input_encoding(self):
+ """
+ QueryDicts must be able to handle invalid input encoding (in this
+ case, bad UTF-8 encoding).
+
+ This test doesn't apply under Python 3 because the URL is a string
+ and not a bytestring.
+ """
+ q = QueryDict(str(b'foo=bar&foo=\xff'))
+ self.assertEqual(q['foo'], '\ufffd')
+ self.assertEqual(q.getlist('foo'), ['bar', '\ufffd'])
def test_pickle(self):
- q = QueryDict('')
+ q = QueryDict(str(''))
q1 = pickle.loads(pickle.dumps(q, 2))
self.assertEqual(q == q1, True)
- q = QueryDict('a=b&c=d')
+ q = QueryDict(str('a=b&c=d'))
q1 = pickle.loads(pickle.dumps(q, 2))
self.assertEqual(q == q1, True)
- q = QueryDict('a=b&c=d&a=1')
+ q = QueryDict(str('a=b&c=d&a=1'))
q1 = pickle.loads(pickle.dumps(q, 2))
self.assertEqual(q == q1, True)
def test_update_from_querydict(self):
"""Regression test for #8278: QueryDict.update(QueryDict)"""
- x = QueryDict("a=1&a=2", mutable=True)
- y = QueryDict("a=3&a=4")
+ x = QueryDict(str("a=1&a=2"), mutable=True)
+ y = QueryDict(str("a=3&a=4"))
x.update(y)
self.assertEqual(x.getlist('a'), ['1', '2', '3', '4'])
def test_non_default_encoding(self):
"""#13572 - QueryDict with a non-default encoding"""
- q = QueryDict(b'sbb=one', encoding='rot_13')
- self.assertEqual(q.encoding , 'rot_13' )
- self.assertEqual(q.items() , [('foo', 'bar')] )
- self.assertEqual(q.urlencode() , 'sbb=one' )
+ q = QueryDict(str('cur=%A4'), encoding='iso-8859-15')
+ self.assertEqual(q.encoding, 'iso-8859-15')
+ self.assertEqual(list(six.iteritems(q)), [('cur', '€')])
+ self.assertEqual(q.urlencode(), 'cur=%A4')
q = q.copy()
- self.assertEqual(q.encoding , 'rot_13' )
- self.assertEqual(q.items() , [('foo', 'bar')] )
- self.assertEqual(q.urlencode() , 'sbb=one' )
- self.assertEqual(copy.copy(q).encoding , 'rot_13' )
- self.assertEqual(copy.deepcopy(q).encoding , 'rot_13')
+ self.assertEqual(q.encoding, 'iso-8859-15')
+ self.assertEqual(list(six.iteritems(q)), [('cur', '€')])
+ self.assertEqual(q.urlencode(), 'cur=%A4')
+ self.assertEqual(copy.copy(q).encoding, 'iso-8859-15')
+ self.assertEqual(copy.deepcopy(q).encoding, 'iso-8859-15')
class HttpResponseTests(unittest.TestCase):
def test_unicode_headers(self):
@@ -260,45 +272,40 @@ class HttpResponseTests(unittest.TestCase):
def test_non_string_content(self):
#Bug 16494: HttpResponse should behave consistently with non-strings
r = HttpResponse(12345)
- self.assertEqual(r.content, '12345')
+ self.assertEqual(r.content, b'12345')
#test content via property
r = HttpResponse()
r.content = 12345
- self.assertEqual(r.content, '12345')
+ self.assertEqual(r.content, b'12345')
def test_iter_content(self):
r = HttpResponse(['abc', 'def', 'ghi'])
- self.assertEqual(r.content, 'abcdefghi')
+ self.assertEqual(r.content, b'abcdefghi')
#test iter content via property
r = HttpResponse()
r.content = ['idan', 'alex', 'jacob']
- self.assertEqual(r.content, 'idanalexjacob')
+ self.assertEqual(r.content, b'idanalexjacob')
r = HttpResponse()
r.content = [1, 2, 3]
- self.assertEqual(r.content, '123')
+ self.assertEqual(r.content, b'123')
#test retrieval explicitly using iter and odd inputs
r = HttpResponse()
- r.content = ['1', '2', 3, unichr(1950)]
- result = []
+ r.content = ['1', '2', 3, '\u079e']
my_iter = r.__iter__()
- while True:
- try:
- result.append(next(my_iter))
- except StopIteration:
- break
+ result = list(my_iter)
#'\xde\x9e' == unichr(1950).encode('utf-8')
- self.assertEqual(result, ['1', '2', '3', b'\xde\x9e'])
+ self.assertEqual(result, [b'1', b'2', b'3', b'\xde\x9e'])
self.assertEqual(r.content, b'123\xde\x9e')
#with Content-Encoding header
r = HttpResponse([1,1,2,4,8])
r['Content-Encoding'] = 'winning'
- self.assertEqual(r.content, '11248')
- r.content = [unichr(1950),]
+ self.assertEqual(r.content, b'11248')
+ r.content = ['\u079e',]
self.assertRaises(UnicodeEncodeError,
getattr, r, 'content')
@@ -325,6 +332,33 @@ class HttpResponseTests(unittest.TestCase):
HttpResponsePermanentRedirect, url)
+class HttpResponseSubclassesTests(TestCase):
+ def test_redirect(self):
+ response = HttpResponseRedirect('/redirected/')
+ self.assertEqual(response.status_code, 302)
+ # Test that standard HttpResponse init args can be used
+ response = HttpResponseRedirect('/redirected/',
+ content='The resource has temporarily moved',
+ content_type='text/html')
+ self.assertContains(response, 'The resource has temporarily moved', status_code=302)
+
+ def test_not_modified(self):
+ response = HttpResponseNotModified()
+ self.assertEqual(response.status_code, 304)
+ # 304 responses should not have content/content-type
+ with self.assertRaises(AttributeError):
+ response.content = "Hello dear"
+ self.assertNotIn('content-type', response)
+
+ def test_not_allowed(self):
+ response = HttpResponseNotAllowed(['GET'])
+ self.assertEqual(response.status_code, 405)
+ # Test that standard HttpResponse init args can be used
+ response = HttpResponseNotAllowed(['GET'],
+ content='Only the GET method is allowed',
+ content_type='text/html')
+ self.assertContains(response, 'Only the GET method is allowed', status_code=405)
+
class CookieTests(unittest.TestCase):
def test_encode(self):
"""
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 9ca66bdb9b..bce3d617d2 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -18,7 +18,7 @@ from django.utils.formats import (get_format, date_format, time_format,
number_format)
from django.utils.importlib import import_module
from django.utils.numberformat import format as nformat
-from django.utils.safestring import mark_safe, SafeString, SafeUnicode
+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,
@@ -232,12 +232,12 @@ class TranslationTests(TestCase):
"""
Translating a string requiring no auto-escaping shouldn't change the "safe" status.
"""
- s = mark_safe(b'Password')
+ s = mark_safe(str('Password'))
self.assertEqual(SafeString, type(s))
with translation.override('de', deactivate=True):
- self.assertEqual(SafeUnicode, type(ugettext(s)))
- self.assertEqual('aPassword', SafeString('a') + s)
- self.assertEqual('Passworda', s + SafeString('a'))
+ self.assertEqual(SafeText, type(ugettext(s)))
+ self.assertEqual('aPassword', SafeText('a') + s)
+ self.assertEqual('Passworda', s + SafeText('a'))
self.assertEqual('Passworda', s + mark_safe('a'))
self.assertEqual('aPassword', mark_safe('a') + s)
self.assertEqual('as', mark_safe('a') + mark_safe('s'))
@@ -808,13 +808,13 @@ class MiscTests(TestCase):
r.META = {'HTTP_ACCEPT_LANGUAGE': 'de'}
self.assertEqual(g(r), 'zh-cn')
- def test_get_language_from_path(self):
+ def test_get_language_from_path_real(self):
from django.utils.translation.trans_real import get_language_from_path as g
self.assertEqual(g('/pl/'), 'pl')
self.assertEqual(g('/pl'), 'pl')
self.assertEqual(g('/xyz/'), None)
- def test_get_language_from_path(self):
+ def test_get_language_from_path_null(self):
from django.utils.translation.trans_null import get_language_from_path as g
self.assertEqual(g('/pl/'), None)
self.assertEqual(g('/pl'), None)
@@ -897,9 +897,9 @@ class TestModels(TestCase):
def test_safestr(self):
c = Company(cents_paid=12, products_delivered=1)
- c.name = SafeUnicode('Iñtërnâtiônàlizætiøn1')
+ c.name = SafeText('Iñtërnâtiônàlizætiøn1')
c.save()
- c.name = SafeString('Iñtërnâtiônàlizætiøn1'.encode('utf-8'))
+ c.name = SafeBytes('Iñtërnâtiônàlizætiøn1'.encode('utf-8'))
c.save()
diff --git a/tests/regressiontests/inline_formsets/models.py b/tests/regressiontests/inline_formsets/models.py
index d76eea758b..40c85fe739 100644
--- a/tests/regressiontests/inline_formsets/models.py
+++ b/tests/regressiontests/inline_formsets/models.py
@@ -1,5 +1,6 @@
# coding: utf-8
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class School(models.Model):
@@ -14,15 +15,17 @@ class Child(models.Model):
school = models.ForeignKey(School)
name = models.CharField(max_length=100)
+@python_2_unicode_compatible
class Poet(models.Model):
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Poem(models.Model):
poet = models.ForeignKey(Poet)
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/regressiontests/inspectdb/models.py b/tests/regressiontests/inspectdb/models.py
index 9f815855b6..4a6621402f 100644
--- a/tests/regressiontests/inspectdb/models.py
+++ b/tests/regressiontests/inspectdb/models.py
@@ -19,3 +19,12 @@ 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
index aae7bc5cc7..484e7f4060 100644
--- a/tests/regressiontests/inspectdb/tests.py
+++ b/tests/regressiontests/inspectdb/tests.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.core.management import call_command
from django.test import TestCase, skipUnlessDBFeature
from django.utils.six import StringIO
@@ -17,7 +19,6 @@ class InspectDBTestCase(TestCase):
# 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)
- out.close()
@skipUnlessDBFeature('can_introspect_foreign_keys')
def test_attribute_name_not_python_keyword(self):
@@ -27,15 +28,16 @@ class InspectDBTestCase(TestCase):
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)", out.getvalue(), msg=error_message)
+ 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')", out.getvalue())
+ self.assertIn("from_field = models.ForeignKey('InspectdbPeople', db_column='from_id')",
+ output)
self.assertIn("people_pk = models.ForeignKey(InspectdbPeople, primary_key=True)",
- out.getvalue())
+ output)
self.assertIn("people_unique = models.ForeignKey(InspectdbPeople, unique=True)",
- out.getvalue())
- out.close()
+ output)
def test_digits_column_name_introspection(self):
"""Introspection of column names consist/start with digits (#16536/#17676)"""
@@ -45,13 +47,27 @@ class InspectDBTestCase(TestCase):
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", out.getvalue(), msg=error_message)
- self.assertIn("number_123 = models.CharField", out.getvalue())
+ 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", out.getvalue(), msg=error_message)
- self.assertIn("number_4extra = models.CharField", out.getvalue())
+ 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)
- self.assertNotIn(" 45extra = models.CharField", out.getvalue(), msg=error_message)
- self.assertIn("number_45extra = models.CharField", out.getvalue())
+ 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()
+ self.assertIn("field = models.IntegerField()", output)
+ self.assertIn("field_field = models.IntegerField(db_column='Field_')", output)
+ self.assertIn("field_field_0 = models.IntegerField(db_column='Field__')", 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/introspection/models.py b/tests/regressiontests/introspection/models.py
index 3ca80c5aab..6e5beba61d 100644
--- a/tests/regressiontests/introspection/models.py
+++ b/tests/regressiontests/introspection/models.py
@@ -1,8 +1,10 @@
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@@ -12,15 +14,16 @@ class Reporter(models.Model):
class Meta:
unique_together = ('first_name', 'last_name')
- def __unicode__(self):
+ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter)
- def __unicode__(self):
+ def __str__(self):
return self.headline
class Meta:
diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
index 06736a68cb..a54e0c670b 100644
--- a/tests/regressiontests/introspection/tests.py
+++ b/tests/regressiontests/introspection/tests.py
@@ -4,6 +4,7 @@ from functools import update_wrapper
from django.db import connection
from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
+from django.utils import six
from .models import Reporter, Article
@@ -35,8 +36,7 @@ class IgnoreNotimplementedError(type):
attrs[k] = ignore_not_implemented(v)
return type.__new__(cls, name, bases, attrs)
-class IntrospectionTests(TestCase):
- __metaclass__ = IgnoreNotimplementedError
+class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)):
def test_table_names(self):
tl = connection.introspection.table_names()
@@ -89,6 +89,11 @@ class IntrospectionTests(TestCase):
[datatype(r[1], r) for r in desc],
['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField']
)
+ # Check also length of CharFields
+ self.assertEqual(
+ [r[3] for r in desc if datatype(r[1], r) == 'CharField'],
+ [30, 30, 75]
+ )
# Oracle forces null=True under the hood in some cases (see
# https://docs.djangoproject.com/en/dev/ref/databases/#null-and-empty-strings)
diff --git a/tests/regressiontests/m2m_regress/models.py b/tests/regressiontests/m2m_regress/models.py
index be8dcae9c1..7c1108456e 100644
--- a/tests/regressiontests/m2m_regress/models.py
+++ b/tests/regressiontests/m2m_regress/models.py
@@ -1,37 +1,42 @@
from django.contrib.auth import models as auth
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# No related name is needed here, since symmetrical relations are not
# explicitly reversible.
+@python_2_unicode_compatible
class SelfRefer(models.Model):
name = models.CharField(max_length=10)
references = models.ManyToManyField('self')
related = models.ManyToManyField('self')
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Tag(models.Model):
name = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return self.name
# Regression for #11956 -- a many to many to the base class
+@python_2_unicode_compatible
class TagCollection(Tag):
tags = models.ManyToManyField(Tag, related_name='tag_collections')
- def __unicode__(self):
+ def __str__(self):
return self.name
# A related_name is required on one of the ManyToManyField entries here because
# they are both addressable as reverse relations from Tag.
+@python_2_unicode_compatible
class Entry(models.Model):
name = models.CharField(max_length=10)
topics = models.ManyToManyField(Tag)
related = models.ManyToManyField(Tag, related_name="similar")
- def __unicode__(self):
+ def __str__(self):
return self.name
# Two models both inheriting from a base model with a self-referential m2m field
diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py
index 1db3de1a4c..47c24ed5b2 100644
--- a/tests/regressiontests/m2m_through_regress/models.py
+++ b/tests/regressiontests/m2m_through_regress/models.py
@@ -2,40 +2,45 @@ from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# Forward declared intermediate model
+@python_2_unicode_compatible
class Membership(models.Model):
person = models.ForeignKey('Person')
group = models.ForeignKey('Group')
price = models.IntegerField(default=100)
- def __unicode__(self):
+ def __str__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)
# using custom id column to test ticket #11107
+@python_2_unicode_compatible
class UserMembership(models.Model):
id = models.AutoField(db_column='usermembership_id', primary_key=True)
user = models.ForeignKey(User)
group = models.ForeignKey('Group')
price = models.IntegerField(default=100)
- def __unicode__(self):
+ def __str__(self):
return "%s is a user and member of %s" % (self.user.username, self.group.name)
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=128)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Group(models.Model):
name = models.CharField(max_length=128)
# Membership object defined as a class
members = models.ManyToManyField(Person, through=Membership)
user_members = models.ManyToManyField(User, through='UserMembership')
- def __unicode__(self):
+ def __str__(self):
return self.name
# A set of models that use an non-abstract inherited model as the 'through' model.
@@ -55,22 +60,25 @@ class B(models.Model):
# Using to_field on the through model
+@python_2_unicode_compatible
class Car(models.Model):
make = models.CharField(max_length=20, unique=True)
drivers = models.ManyToManyField('Driver', through='CarDriver')
- def __unicode__(self, ):
+ def __str__(self):
return self.make
+@python_2_unicode_compatible
class Driver(models.Model):
name = models.CharField(max_length=20, unique=True)
- def __unicode__(self, ):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class CarDriver(models.Model):
car = models.ForeignKey('Car', to_field='make')
driver = models.ForeignKey('Driver', to_field='name')
- def __unicode__(self, ):
+ def __str__(self):
return "pk=%s car=%s driver=%s" % (str(self.pk), self.car, self.driver)
diff --git a/tests/regressiontests/m2m_through_regress/tests.py b/tests/regressiontests/m2m_through_regress/tests.py
index 73a13654e7..458c194f89 100644
--- a/tests/regressiontests/m2m_through_regress/tests.py
+++ b/tests/regressiontests/m2m_through_regress/tests.py
@@ -1,10 +1,9 @@
from __future__ import absolute_import
-from io import BytesIO
-
from django.core import management
from django.contrib.auth.models import User
from django.test import TestCase
+from django.utils.six import StringIO
from .models import (Person, Group, Membership, UserMembership, Car, Driver,
CarDriver)
@@ -70,11 +69,11 @@ class M2MThroughTestCase(TestCase):
pks = {"p_pk": p.pk, "g_pk": g.pk, "m_pk": m.pk}
- out = BytesIO()
+ out = StringIO()
management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out)
self.assertEqual(out.getvalue().strip(), """[{"pk": %(m_pk)s, "model": "m2m_through_regress.membership", "fields": {"person": %(p_pk)s, "price": 100, "group": %(g_pk)s}}, {"pk": %(p_pk)s, "model": "m2m_through_regress.person", "fields": {"name": "Bob"}}, {"pk": %(g_pk)s, "model": "m2m_through_regress.group", "fields": {"name": "Roll"}}]""" % pks)
- out = BytesIO()
+ out = StringIO()
management.call_command("dumpdata", "m2m_through_regress", format="xml",
indent=2, stdout=out)
self.assertEqual(out.getvalue().strip(), """
@@ -142,6 +141,6 @@ class ThroughLoadDataTestCase(TestCase):
def test_sequence_creation(self):
"Check that sequences on an m2m_through are created for the through model, not a phantom auto-generated m2m table. Refs #11107"
- out = BytesIO()
+ out = StringIO()
management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out)
self.assertEqual(out.getvalue().strip(), """[{"pk": 1, "model": "m2m_through_regress.usermembership", "fields": {"price": 100, "group": 1, "user": 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Guido"}}, {"pk": 1, "model": "m2m_through_regress.group", "fields": {"name": "Python Core Group"}}]""")
diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py
index c948662bc3..3e9ae84650 100644
--- a/tests/regressiontests/mail/tests.py
+++ b/tests/regressiontests/mail/tests.py
@@ -553,6 +553,8 @@ class FileBackendTests(BaseEmailBackendTests, TestCase):
msg.send()
self.assertEqual(len(os.listdir(self.tmp_dir)), 3)
+ connection.close()
+
class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
email_backend = 'django.core.mail.backends.console.EmailBackend'
diff --git a/tests/regressiontests/managers_regress/models.py b/tests/regressiontests/managers_regress/models.py
index fb6c530722..892505f24a 100644
--- a/tests/regressiontests/managers_regress/models.py
+++ b/tests/regressiontests/managers_regress/models.py
@@ -3,6 +3,7 @@ Various edge-cases for model managers.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class OnlyFred(models.Manager):
@@ -44,35 +45,40 @@ class AbstractBase3(models.Model):
class Meta:
abstract = True
+@python_2_unicode_compatible
class Parent(models.Model):
name = models.CharField(max_length=50)
manager = OnlyFred()
- def __unicode__(self):
+ def __str__(self):
return self.name
# Managers from base classes are inherited and, if no manager is specified
# *and* the parent has a manager specified, the first one (in the MRO) will
# become the default.
+@python_2_unicode_compatible
class Child1(AbstractBase1):
data = models.CharField(max_length=25)
- def __unicode__(self):
+ def __str__(self):
return self.data
+@python_2_unicode_compatible
class Child2(AbstractBase1, AbstractBase2):
data = models.CharField(max_length=25)
- def __unicode__(self):
+ def __str__(self):
return self.data
+@python_2_unicode_compatible
class Child3(AbstractBase1, AbstractBase3):
data = models.CharField(max_length=25)
- def __unicode__(self):
+ def __str__(self):
return self.data
+@python_2_unicode_compatible
class Child4(AbstractBase1):
data = models.CharField(max_length=25)
@@ -80,16 +86,17 @@ class Child4(AbstractBase1):
# inherited.
default = models.Manager()
- def __unicode__(self):
+ def __str__(self):
return self.data
+@python_2_unicode_compatible
class Child5(AbstractBase3):
name = models.CharField(max_length=25)
default = OnlyFred()
objects = models.Manager()
- def __unicode__(self):
+ def __str__(self):
return self.name
# Will inherit managers from AbstractBase1, but not Child4.
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 1e59c4c8c8..f3727820f8 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -4,6 +4,7 @@ Regression tests for a few ForeignKey bugs.
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# If ticket #1578 ever slips back in, these models will not be able to be
# created (the field names being lower-cased versions of their opposite
@@ -30,18 +31,20 @@ class Child(models.Model):
# Multiple paths to the same model (#7110, #7125)
+@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Record(models.Model):
category = models.ForeignKey(Category)
+@python_2_unicode_compatible
class Relation(models.Model):
left = models.ForeignKey(Record, related_name='left_set')
right = models.ForeignKey(Record, related_name='right_set')
- def __unicode__(self):
+ def __str__(self):
return "%s - %s" % (self.left.category.name, self.right.category.name)
diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py
index 08a385e6cf..eb66f2bbf3 100644
--- a/tests/regressiontests/middleware/tests.py
+++ b/tests/regressiontests/middleware/tests.py
@@ -3,6 +3,7 @@
import gzip
import re
import random
+from io import BytesIO
from django.conf import settings
from django.core import mail
@@ -14,8 +15,9 @@ from django.middleware.http import ConditionalGetMiddleware
from django.middleware.gzip import GZipMiddleware
from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
+from django.utils import six
from django.utils.six.moves import xrange
-from django.utils.six import StringIO
+
class CommonMiddlewareTest(TestCase):
def setUp(self):
@@ -506,9 +508,9 @@ class GZipMiddlewareTest(TestCase):
"""
Tests the GZip middleware.
"""
- short_string = "This string is too short to be worth compressing."
- compressible_string = 'a' * 500
- uncompressible_string = ''.join(chr(random.randint(0, 255)) for _ in xrange(500))
+ short_string = b"This string is too short to be worth compressing."
+ compressible_string = b'a' * 500
+ uncompressible_string = b''.join(six.int2byte(random.randint(0, 255)) for _ in xrange(500))
def setUp(self):
self.req = HttpRequest()
@@ -526,7 +528,7 @@ class GZipMiddlewareTest(TestCase):
@staticmethod
def decompress(gzipped_string):
- return gzip.GzipFile(mode='rb', fileobj=StringIO(gzipped_string)).read()
+ return gzip.GzipFile(mode='rb', fileobj=BytesIO(gzipped_string)).read()
def test_compress_response(self):
"""
@@ -590,7 +592,7 @@ class ETagGZipMiddlewareTest(TestCase):
"""
Tests if the ETag middleware behaves correctly with GZip middleware.
"""
- compressible_string = 'a' * 500
+ compressible_string = b'a' * 500
def setUp(self):
self.rf = RequestFactory()
diff --git a/tests/regressiontests/model_fields/models.py b/tests/regressiontests/model_fields/models.py
index 4dcfb17bb3..d9c123fb39 100644
--- a/tests/regressiontests/model_fields/models.py
+++ b/tests/regressiontests/model_fields/models.py
@@ -69,6 +69,33 @@ class BooleanModel(models.Model):
class RenamedField(models.Model):
modelname = models.IntegerField(name="fieldname", choices=((1,'One'),))
+class VerboseNameField(models.Model):
+ id = models.AutoField("verbose pk", primary_key=True)
+ field1 = models.BigIntegerField("verbose field1")
+ field2 = models.BooleanField("verbose field2")
+ field3 = models.CharField("verbose field3", max_length=10)
+ field4 = models.CommaSeparatedIntegerField("verbose field4", max_length=99)
+ field5 = models.DateField("verbose field5")
+ field6 = models.DateTimeField("verbose field6")
+ field7 = models.DecimalField("verbose field7", max_digits=6, decimal_places=1)
+ field8 = models.EmailField("verbose field8")
+ field9 = models.FileField("verbose field9", upload_to="unused")
+ field10 = models.FilePathField("verbose field10")
+ field11 = models.FloatField("verbose field11")
+ # Don't want to depend on PIL in this test
+ #field_image = models.ImageField("verbose field")
+ field12 = models.IntegerField("verbose field12")
+ field13 = models.IPAddressField("verbose field13")
+ field14 = models.GenericIPAddressField("verbose field14", protocol="ipv4")
+ field15 = models.NullBooleanField("verbose field15")
+ field16 = models.PositiveIntegerField("verbose field16")
+ field17 = models.PositiveSmallIntegerField("verbose field17")
+ field18 = models.SlugField("verbose field18")
+ field19 = models.SmallIntegerField("verbose field19")
+ field20 = models.TextField("verbose field20")
+ field21 = models.TimeField("verbose field21")
+ field22 = models.URLField("verbose field22")
+
# This model isn't used in any test, just here to ensure it validates successfully.
# See ticket #16570.
class DecimalLessThanOne(models.Model):
diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py
index 7d6071accc..526aca4f98 100644
--- a/tests/regressiontests/model_fields/tests.py
+++ b/tests/regressiontests/model_fields/tests.py
@@ -12,7 +12,7 @@ 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)
+ NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField)
from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests,
TwoImageFieldTests, ImageFieldNoDimensionsTests,
@@ -64,6 +64,14 @@ class BasicFieldTests(test.TestCase):
self.assertTrue(hasattr(instance, 'get_fieldname_display'))
self.assertFalse(hasattr(instance, 'get_modelname_display'))
+ def test_field_verbose_name(self):
+ m = VerboseNameField
+ for i in range(1, 23):
+ self.assertEqual(m._meta.get_field('field%d' % i).verbose_name,
+ 'verbose field%d' % i)
+
+ self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')
+
class DecimalFieldTests(test.TestCase):
def test_to_python(self):
f = models.DecimalField(max_digits=4, decimal_places=2)
diff --git a/tests/regressiontests/model_forms_regress/models.py b/tests/regressiontests/model_forms_regress/models.py
index 9259e260f0..f6e08d24dc 100644
--- a/tests/regressiontests/model_forms_regress/models.py
+++ b/tests/regressiontests/model_forms_regress/models.py
@@ -4,6 +4,7 @@ import os
from django.core.exceptions import ValidationError
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class Person(models.Model):
@@ -20,18 +21,20 @@ class Triple(models.Model):
class FilePathModel(models.Model):
path = models.FilePathField(path=os.path.dirname(__file__), match=".*\.py$", blank=True)
+@python_2_unicode_compatible
class Publication(models.Model):
title = models.CharField(max_length=30)
date_published = models.DateField()
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
- def __unicode__(self):
+ def __str__(self):
return self.headline
class CustomFileField(models.FileField):
diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py
index 3cb129f84e..90c907f2a6 100644
--- a/tests/regressiontests/model_forms_regress/tests.py
+++ b/tests/regressiontests/model_forms_regress/tests.py
@@ -485,9 +485,8 @@ class CustomMetaclass(ModelFormMetaclass):
new.base_fields = {}
return new
-class CustomMetaclassForm(forms.ModelForm):
- __metaclass__ = CustomMetaclass
-
+class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)):
+ pass
class CustomMetaclassTestCase(TestCase):
def test_modelform_factory_metaclass(self):
diff --git a/tests/regressiontests/model_formsets_regress/models.py b/tests/regressiontests/model_formsets_regress/models.py
index 189ed8072e..f94ad51929 100644
--- a/tests/regressiontests/model_formsets_regress/models.py
+++ b/tests/regressiontests/model_formsets_regress/models.py
@@ -1,4 +1,5 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class User(models.Model):
@@ -22,9 +23,10 @@ class Manager(models.Model):
class Network(models.Model):
name = models.CharField(max_length=15)
+@python_2_unicode_compatible
class Host(models.Model):
network = models.ForeignKey(Network)
hostname = models.CharField(max_length=25)
- def __unicode__(self):
+ def __str__(self):
return self.hostname
diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py
index 5481298963..811c8175bb 100644
--- a/tests/regressiontests/model_inheritance_regress/models.py
+++ b/tests/regressiontests/model_inheritance_regress/models.py
@@ -3,7 +3,9 @@ from __future__ import unicode_literals
import datetime
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
@@ -11,28 +13,31 @@ class Place(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return "%s the place" % self.name
+@python_2_unicode_compatible
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the restaurant" % self.name
+@python_2_unicode_compatible
class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the italian restaurant" % self.name
+@python_2_unicode_compatible
class ParkingLot(Place):
# An explicit link to the parent (we can control the attribute name).
parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
capacity = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return "%s the parking lot" % self.name
class ParkingLot2(Place):
@@ -64,13 +69,14 @@ class SelfRefParent(models.Model):
class SelfRefChild(SelfRefParent):
child_data = models.IntegerField()
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
class Meta:
ordering = ('-pub_date', 'headline')
- def __unicode__(self):
+ def __str__(self):
return self.headline
class ArticleWithAuthor(Article):
@@ -91,17 +97,19 @@ class Evaluation(Article):
class QualityControl(Evaluation):
assignee = models.CharField(max_length=50)
+@python_2_unicode_compatible
class BaseM(models.Model):
base_name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.base_name
+@python_2_unicode_compatible
class DerivedM(BaseM):
customPK = models.IntegerField(primary_key=True)
derived_name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return "PK = %d, base_name = %s, derived_name = %s" \
% (self.customPK, self.base_name, self.derived_name)
@@ -120,15 +128,17 @@ class InternalCertificationAudit(CertificationAudit):
auditing_dept = models.CharField(max_length=20)
# Check that abstract classes don't get m2m tables autocreated.
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class AbstractEvent(models.Model):
name = models.CharField(max_length=100)
attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
@@ -137,7 +147,7 @@ class AbstractEvent(models.Model):
abstract = True
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
class BirthdayParty(AbstractEvent):
diff --git a/tests/regressiontests/model_inheritance_select_related/models.py b/tests/regressiontests/model_inheritance_select_related/models.py
index f810531bff..6b28772620 100644
--- a/tests/regressiontests/model_inheritance_select_related/models.py
+++ b/tests/regressiontests/model_inheritance_select_related/models.py
@@ -5,27 +5,31 @@ select_related().
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=50)
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return "%s the place" % self.name
+@python_2_unicode_compatible
class Restaurant(Place):
serves_sushi = models.BooleanField()
serves_steak = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the restaurant" % self.name
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=50)
favorite_restaurant = models.ForeignKey(Restaurant)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py
index aca6d44837..82efb9c783 100644
--- a/tests/regressiontests/model_regress/models.py
+++ b/tests/regressiontests/model_regress/models.py
@@ -1,5 +1,6 @@
# coding: utf-8
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
CHOICES = (
@@ -8,6 +9,7 @@ CHOICES = (
)
+@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100, default='Default headline')
pub_date = models.DateTimeField()
@@ -20,7 +22,7 @@ class Article(models.Model):
# A utf-8 verbose name (Ångström's Articles) to test they are valid.
verbose_name = "\xc3\x85ngstr\xc3\xb6m's Articles"
- def __unicode__(self):
+ def __str__(self):
return self.headline
@@ -38,29 +40,31 @@ class Event(models.Model):
when = models.DateTimeField()
+@python_2_unicode_compatible
class Department(models.Model):
id = models.PositiveIntegerField(primary_key=True)
name = models.CharField(max_length=200)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Worker(models.Model):
department = models.ForeignKey(Department)
name = models.CharField(max_length=200)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class BrokenUnicodeMethod(models.Model):
name = models.CharField(max_length=7)
- def __unicode__(self):
- # Intentionally broken (trying to insert a unicode value into a str
- # object).
- return 'Názov: %s' % self.name
+ def __str__(self):
+ # Intentionally broken (invalid start byte in byte string).
+ return b'Name\xff: %s'.decode() % self.name
class NonAutoPK(models.Model):
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
index 33202fad8f..fdbcabd187 100644
--- a/tests/regressiontests/modeladmin/models.py
+++ b/tests/regressiontests/modeladmin/models.py
@@ -1,8 +1,10 @@
# coding: utf-8
from django.contrib.auth.models import User
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)
bio = models.TextField()
@@ -11,7 +13,7 @@ class Band(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Concert(models.Model):
diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py
index c2a1ca05ba..d55d50d0a5 100644
--- a/tests/regressiontests/modeladmin/tests.py
+++ b/tests/regressiontests/modeladmin/tests.py
@@ -5,7 +5,7 @@ from datetime import date
from django import forms
from django.conf import settings
from django.contrib.admin.options import (ModelAdmin, TabularInline,
- InlineModelAdmin, HORIZONTAL, VERTICAL)
+ HORIZONTAL, VERTICAL)
from django.contrib.admin.sites import AdminSite
from django.contrib.admin.validation import validate
from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
@@ -15,7 +15,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.forms.models import BaseModelFormSet
from django.forms.widgets import Select
from django.test import TestCase
-from django.test.utils import override_settings, str_prefix
+from django.test.utils import str_prefix
from django.utils import unittest
from .models import Band, Concert, ValidationTestModel, ValidationTestInlineModel
@@ -24,6 +24,7 @@ from .models import Band, Concert, ValidationTestModel, ValidationTestInlineMode
class MockRequest(object):
pass
+
class MockSuperUser(object):
def has_perm(self, perm):
return True
@@ -47,7 +48,7 @@ class ModelAdminTests(TestCase):
def test_default_fields(self):
ma = ModelAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['name', 'bio', 'sign_date'])
def test_default_fieldsets(self):
@@ -72,7 +73,7 @@ class ModelAdminTests(TestCase):
ma = BandAdmin(Band, self.site)
- self.assertEqual( ma.get_fieldsets(request),
+ self.assertEqual(ma.get_fieldsets(request),
[(None, {'fields': ['name']})])
self.assertEqual(ma.get_fieldsets(request, self.band),
@@ -90,8 +91,8 @@ class ModelAdminTests(TestCase):
fields = ['name']
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(), ['name'])
- self.assertEqual(ma.get_form(request, self.band).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
+ self.assertEqual(list(ma.get_form(request, self.band).base_fields),
['name'])
# Using `fieldsets`.
@@ -99,8 +100,8 @@ class ModelAdminTests(TestCase):
fieldsets = [(None, {'fields': ['name']})]
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(), ['name'])
- self.assertEqual(ma.get_form(request, self.band).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
+ self.assertEqual(list(ma.get_form(request, self.band).base_fields),
['name'])
# Using `exclude`.
@@ -108,7 +109,7 @@ class ModelAdminTests(TestCase):
exclude = ['bio']
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['name', 'sign_date'])
# You can also pass a tuple to `exclude`.
@@ -116,7 +117,7 @@ class ModelAdminTests(TestCase):
exclude = ('bio',)
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['name', 'sign_date'])
# Using `fields` and `exclude`.
@@ -125,7 +126,7 @@ class ModelAdminTests(TestCase):
exclude = ['bio']
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['name'])
def test_custom_form_meta_exclude_with_readonly(self):
@@ -148,8 +149,8 @@ class ModelAdminTests(TestCase):
form = AdminBandForm
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
- ['sign_date',])
+ self.assertEqual(list(ma.get_form(request).base_fields),
+ ['sign_date'])
# Then, with `InlineModelAdmin` -----------------
@@ -172,8 +173,8 @@ class ModelAdminTests(TestCase):
ma = BandAdmin(Band, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
- ['main_band', 'opening_band', 'id', 'DELETE',])
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
+ ['main_band', 'opening_band', 'id', 'DELETE'])
def test_custom_form_meta_exclude(self):
"""
@@ -194,8 +195,8 @@ class ModelAdminTests(TestCase):
form = AdminBandForm
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
- ['bio', 'sign_date',])
+ self.assertEqual(list(ma.get_form(request).base_fields),
+ ['bio', 'sign_date'])
# Then, with `InlineModelAdmin` -----------------
@@ -218,8 +219,8 @@ class ModelAdminTests(TestCase):
ma = BandAdmin(Band, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
- ['main_band', 'opening_band', 'day', 'id', 'DELETE',])
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
+ ['main_band', 'opening_band', 'day', 'id', 'DELETE'])
def test_custom_form_validation(self):
# If we specify a form, it should use it allowing custom validation to work
@@ -235,7 +236,7 @@ class ModelAdminTests(TestCase):
form = AdminBandForm
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['name', 'bio', 'sign_date', 'delete'])
self.assertEqual(
@@ -255,7 +256,7 @@ class ModelAdminTests(TestCase):
exclude = ['name']
class BandAdmin(ModelAdmin):
- exclude = ['sign_date',]
+ exclude = ['sign_date']
form = AdminBandForm
def get_form(self, request, obj=None, **kwargs):
@@ -263,9 +264,8 @@ class ModelAdminTests(TestCase):
return super(BandAdmin, self).get_form(request, obj, **kwargs)
ma = BandAdmin(Band, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
- ['name', 'sign_date',])
-
+ self.assertEqual(list(ma.get_form(request).base_fields),
+ ['name', 'sign_date'])
def test_formset_exclude_kwarg_override(self):
"""
@@ -296,8 +296,8 @@ class ModelAdminTests(TestCase):
ma = BandAdmin(Band, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
- ['main_band', 'day', 'transport', 'id', 'DELETE',])
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
+ ['main_band', 'day', 'transport', 'id', 'DELETE'])
def test_queryset_override(self):
# If we need to override the queryset of a ModelChoiceField in our custom form
@@ -461,7 +461,7 @@ class ModelAdminTests(TestCase):
form = AdminConcertForm
ma = ConcertAdmin(Concert, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['main_band', 'opening_band', 'day'])
class AdminConcertForm(forms.ModelForm):
@@ -475,7 +475,7 @@ class ModelAdminTests(TestCase):
form = AdminConcertForm
ma = ConcertAdmin(Concert, self.site)
- self.assertEqual(ma.get_form(request).base_fields.keys(),
+ self.assertEqual(list(ma.get_form(request).base_fields),
['extra', 'transport'])
class ConcertInline(TabularInline):
@@ -491,7 +491,7 @@ class ModelAdminTests(TestCase):
ma = BandAdmin(Band, self.site)
self.assertEqual(
- list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
+ list(list(ma.get_formsets(request))[0]().forms[0].fields),
['extra', 'transport', 'id', 'DELETE', 'main_band'])
diff --git a/tests/regressiontests/multiple_database/models.py b/tests/regressiontests/multiple_database/models.py
index 7d655fe3d6..e46438649b 100644
--- a/tests/regressiontests/multiple_database/models.py
+++ b/tests/regressiontests/multiple_database/models.py
@@ -4,15 +4,17 @@ from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Review(models.Model):
source = models.CharField(max_length=100)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
- def __unicode__(self):
+ def __str__(self):
return self.source
class Meta:
@@ -22,11 +24,12 @@ class PersonManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
+@python_2_unicode_compatible
class Person(models.Model):
objects = PersonManager()
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
@@ -45,6 +48,7 @@ class BookManager(models.Manager):
kwargs.pop('extra_arg', None)
return super(BookManager, self).get_or_create(*args, **kwargs)
+@python_2_unicode_compatible
class Book(models.Model):
objects = BookManager()
title = models.CharField(max_length=100)
@@ -54,17 +58,18 @@ class Book(models.Model):
reviews = generic.GenericRelation(Review)
pages = models.IntegerField(default=100)
- def __unicode__(self):
+ def __str__(self):
return self.title
class Meta:
ordering = ('title',)
+@python_2_unicode_compatible
class Pet(models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(Person)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Meta:
diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py
index 74a5f2f550..782fe2bfc6 100644
--- a/tests/regressiontests/multiple_database/tests.py
+++ b/tests/regressiontests/multiple_database/tests.py
@@ -1546,6 +1546,21 @@ class RouterTestCase(TestCase):
# If you evaluate the query, it should work, running on 'other'
self.assertEqual(list(qs.values_list('title', flat=True)), ['Dive into Python'])
+ def test_deferred_models(self):
+ mark_def = Person.objects.using('default').create(name="Mark Pilgrim")
+ mark_other = Person.objects.using('other').create(name="Mark Pilgrim")
+ orig_b = Book.objects.using('other').create(title="Dive into Python",
+ published=datetime.date(2009, 5, 4),
+ editor=mark_other)
+ b = Book.objects.using('other').only('title').get(pk=orig_b.pk)
+ self.assertEqual(b.published, datetime.date(2009, 5, 4))
+ b = Book.objects.using('other').only('title').get(pk=orig_b.pk)
+ b.editor = mark_def
+ b.save(using='default')
+ self.assertEqual(Book.objects.using('default').get(pk=b.pk).published,
+ datetime.date(2009, 5, 4))
+
+
class AuthTestCase(TestCase):
multi_db = True
diff --git a/tests/regressiontests/nested_foreign_keys/__init__.py b/tests/regressiontests/nested_foreign_keys/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/nested_foreign_keys/__init__.py
diff --git a/tests/regressiontests/nested_foreign_keys/models.py b/tests/regressiontests/nested_foreign_keys/models.py
new file mode 100644
index 0000000000..50d447951b
--- /dev/null
+++ b/tests/regressiontests/nested_foreign_keys/models.py
@@ -0,0 +1,28 @@
+from django.db import models
+
+
+class Person(models.Model):
+ name = models.CharField(max_length=200)
+
+
+class Movie(models.Model):
+ title = models.CharField(max_length=200)
+ director = models.ForeignKey(Person)
+
+
+class Event(models.Model):
+ pass
+
+
+class Screening(Event):
+ movie = models.ForeignKey(Movie)
+
+class ScreeningNullFK(Event):
+ movie = models.ForeignKey(Movie, null=True)
+
+
+class Package(models.Model):
+ screening = models.ForeignKey(Screening, null=True)
+
+class PackageNullFK(models.Model):
+ screening = models.ForeignKey(ScreeningNullFK, null=True)
diff --git a/tests/regressiontests/nested_foreign_keys/tests.py b/tests/regressiontests/nested_foreign_keys/tests.py
new file mode 100644
index 0000000000..a976d12453
--- /dev/null
+++ b/tests/regressiontests/nested_foreign_keys/tests.py
@@ -0,0 +1,166 @@
+from __future__ import absolute_import
+from django.test import TestCase
+
+from .models import Person, Movie, Event, Screening, ScreeningNullFK, Package, PackageNullFK
+
+
+# These are tests for #16715. The basic scheme is always the same: 3 models with
+# 2 relations. The first relation may be null, while the second is non-nullable.
+# In some cases, Django would pick the wrong join type for the second relation,
+# resulting in missing objects in the queryset.
+#
+# Model A
+# | (Relation A/B : nullable)
+# Model B
+# | (Relation B/C : non-nullable)
+# Model C
+#
+# Because of the possibility of NULL rows resulting from the LEFT OUTER JOIN
+# between Model A and Model B (i.e. instances of A without reference to B),
+# the second join must also be LEFT OUTER JOIN, so that we do not ignore
+# instances of A that do not reference B.
+#
+# Relation A/B can either be an explicit foreign key or an implicit reverse
+# relation such as introduced by one-to-one relations (through multi-table
+# inheritance).
+class NestedForeignKeysTests(TestCase):
+ def setUp(self):
+ self.director = Person.objects.create(name='Terry Gilliam / Terry Jones')
+ self.movie = Movie.objects.create(title='Monty Python and the Holy Grail', director=self.director)
+
+
+ # This test failed in #16715 because in some cases INNER JOIN was selected
+ # for the second foreign key relation instead of LEFT OUTER JOIN.
+ def testInheritance(self):
+ some_event = Event.objects.create()
+ screening = Screening.objects.create(movie=self.movie)
+
+ self.assertEqual(len(Event.objects.all()), 2)
+ self.assertEqual(len(Event.objects.select_related('screening')), 2)
+ # This failed.
+ self.assertEqual(len(Event.objects.select_related('screening__movie')), 2)
+
+ self.assertEqual(len(Event.objects.values()), 2)
+ self.assertEqual(len(Event.objects.values('screening__pk')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__pk')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__title')), 2)
+ # This failed.
+ self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__title')), 2)
+
+ # Simple filter/exclude queries for good measure.
+ self.assertEqual(Event.objects.filter(screening__movie=self.movie).count(), 1)
+ self.assertEqual(Event.objects.exclude(screening__movie=self.movie).count(), 1)
+
+
+ # These all work because the second foreign key in the chain has null=True.
+ def testInheritanceNullFK(self):
+ some_event = Event.objects.create()
+ screening = ScreeningNullFK.objects.create(movie=None)
+ screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
+
+ self.assertEqual(len(Event.objects.all()), 3)
+ self.assertEqual(len(Event.objects.select_related('screeningnullfk')), 3)
+ self.assertEqual(len(Event.objects.select_related('screeningnullfk__movie')), 3)
+
+ self.assertEqual(len(Event.objects.values()), 3)
+ self.assertEqual(len(Event.objects.values('screeningnullfk__pk')), 3)
+ self.assertEqual(len(Event.objects.values('screeningnullfk__movie__pk')), 3)
+ self.assertEqual(len(Event.objects.values('screeningnullfk__movie__title')), 3)
+ self.assertEqual(len(Event.objects.values('screeningnullfk__movie__pk', 'screeningnullfk__movie__title')), 3)
+
+ self.assertEqual(Event.objects.filter(screeningnullfk__movie=self.movie).count(), 1)
+ self.assertEqual(Event.objects.exclude(screeningnullfk__movie=self.movie).count(), 2)
+
+
+ # This test failed in #16715 because in some cases INNER JOIN was selected
+ # for the second foreign key relation instead of LEFT OUTER JOIN.
+ def testExplicitForeignKey(self):
+ package = Package.objects.create()
+ screening = Screening.objects.create(movie=self.movie)
+ package_with_screening = Package.objects.create(screening=screening)
+
+ self.assertEqual(len(Package.objects.all()), 2)
+ self.assertEqual(len(Package.objects.select_related('screening')), 2)
+ self.assertEqual(len(Package.objects.select_related('screening__movie')), 2)
+
+ self.assertEqual(len(Package.objects.values()), 2)
+ self.assertEqual(len(Package.objects.values('screening__pk')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__pk')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__title')), 2)
+ # This failed.
+ self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__title')), 2)
+
+ self.assertEqual(Package.objects.filter(screening__movie=self.movie).count(), 1)
+ self.assertEqual(Package.objects.exclude(screening__movie=self.movie).count(), 1)
+
+
+ # These all work because the second foreign key in the chain has null=True.
+ def testExplicitForeignKeyNullFK(self):
+ package = PackageNullFK.objects.create()
+ screening = ScreeningNullFK.objects.create(movie=None)
+ screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
+ package_with_screening = PackageNullFK.objects.create(screening=screening)
+ package_with_screening_with_movie = PackageNullFK.objects.create(screening=screening_with_movie)
+
+ self.assertEqual(len(PackageNullFK.objects.all()), 3)
+ self.assertEqual(len(PackageNullFK.objects.select_related('screening')), 3)
+ self.assertEqual(len(PackageNullFK.objects.select_related('screening__movie')), 3)
+
+ self.assertEqual(len(PackageNullFK.objects.values()), 3)
+ self.assertEqual(len(PackageNullFK.objects.values('screening__pk')), 3)
+ self.assertEqual(len(PackageNullFK.objects.values('screening__movie__pk')), 3)
+ self.assertEqual(len(PackageNullFK.objects.values('screening__movie__title')), 3)
+ self.assertEqual(len(PackageNullFK.objects.values('screening__movie__pk', 'screening__movie__title')), 3)
+
+ self.assertEqual(PackageNullFK.objects.filter(screening__movie=self.movie).count(), 1)
+ self.assertEqual(PackageNullFK.objects.exclude(screening__movie=self.movie).count(), 2)
+
+
+# Some additional tests for #16715. The only difference is the depth of the
+# nesting as we now use 4 models instead of 3 (and thus 3 relations). This
+# checks if promotion of join types works for deeper nesting too.
+class DeeplyNestedForeignKeysTests(TestCase):
+ def setUp(self):
+ self.director = Person.objects.create(name='Terry Gilliam / Terry Jones')
+ self.movie = Movie.objects.create(title='Monty Python and the Holy Grail', director=self.director)
+
+
+ def testInheritance(self):
+ some_event = Event.objects.create()
+ screening = Screening.objects.create(movie=self.movie)
+
+ self.assertEqual(len(Event.objects.all()), 2)
+ self.assertEqual(len(Event.objects.select_related('screening__movie__director')), 2)
+
+ self.assertEqual(len(Event.objects.values()), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__director__pk')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__director__name')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__director__pk', 'screening__movie__director__name')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__director__pk')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__pk', 'screening__movie__director__name')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__title', 'screening__movie__director__pk')), 2)
+ self.assertEqual(len(Event.objects.values('screening__movie__title', 'screening__movie__director__name')), 2)
+
+ self.assertEqual(Event.objects.filter(screening__movie__director=self.director).count(), 1)
+ self.assertEqual(Event.objects.exclude(screening__movie__director=self.director).count(), 1)
+
+
+ def testExplicitForeignKey(self):
+ package = Package.objects.create()
+ screening = Screening.objects.create(movie=self.movie)
+ package_with_screening = Package.objects.create(screening=screening)
+
+ self.assertEqual(len(Package.objects.all()), 2)
+ self.assertEqual(len(Package.objects.select_related('screening__movie__director')), 2)
+
+ self.assertEqual(len(Package.objects.values()), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__director__pk')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__director__name')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__director__pk', 'screening__movie__director__name')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__director__pk')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__pk', 'screening__movie__director__name')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__title', 'screening__movie__director__pk')), 2)
+ self.assertEqual(len(Package.objects.values('screening__movie__title', 'screening__movie__director__name')), 2)
+
+ self.assertEqual(Package.objects.filter(screening__movie__director=self.director).count(), 1)
+ self.assertEqual(Package.objects.exclude(screening__movie__director=self.director).count(), 1)
diff --git a/tests/regressiontests/null_fk/models.py b/tests/regressiontests/null_fk/models.py
index e32ff542a0..c86ee8a5a9 100644
--- a/tests/regressiontests/null_fk/models.py
+++ b/tests/regressiontests/null_fk/models.py
@@ -3,6 +3,7 @@ Regression tests for proper working of ForeignKey(null=True).
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
class SystemDetails(models.Model):
@@ -16,13 +17,15 @@ class Forum(models.Model):
system_info = models.ForeignKey(SystemInfo)
forum_name = models.CharField(max_length=32)
+@python_2_unicode_compatible
class Post(models.Model):
forum = models.ForeignKey(Forum, null=True)
title = models.CharField(max_length=32)
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class Comment(models.Model):
post = models.ForeignKey(Post, null=True)
comment_text = models.CharField(max_length=250)
@@ -30,7 +33,7 @@ class Comment(models.Model):
class Meta:
ordering = ('comment_text',)
- def __unicode__(self):
+ def __str__(self):
return self.comment_text
# Ticket 15823
diff --git a/tests/regressiontests/null_fk_ordering/models.py b/tests/regressiontests/null_fk_ordering/models.py
index e4a19f1512..3caff0d594 100644
--- a/tests/regressiontests/null_fk_ordering/models.py
+++ b/tests/regressiontests/null_fk_ordering/models.py
@@ -8,17 +8,19 @@ xpected results
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# The first two models represent a very simple null FK ordering case.
class Author(models.Model):
name = models.CharField(max_length=150)
+@python_2_unicode_compatible
class Article(models.Model):
title = models.CharField(max_length=150)
author = models.ForeignKey(Author, null=True)
- def __unicode__(self):
+ def __str__(self):
return 'Article titled: %s' % (self.title, )
class Meta:
@@ -33,13 +35,15 @@ class Forum(models.Model):
system_info = models.ForeignKey(SystemInfo)
forum_name = models.CharField(max_length=32)
+@python_2_unicode_compatible
class Post(models.Model):
forum = models.ForeignKey(Forum, null=True)
title = models.CharField(max_length=32)
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class Comment(models.Model):
post = models.ForeignKey(Post, null=True)
comment_text = models.CharField(max_length=250)
@@ -47,5 +51,5 @@ class Comment(models.Model):
class Meta:
ordering = ['post__forum__system_info__system_name', 'comment_text']
- def __unicode__(self):
+ def __str__(self):
return self.comment_text
diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py
index 886bd75276..25560fbab7 100644
--- a/tests/regressiontests/null_queries/models.py
+++ b/tests/regressiontests/null_queries/models.py
@@ -1,19 +1,22 @@
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Poll(models.Model):
question = models.CharField(max_length=200)
- def __unicode__(self):
+ def __str__(self):
return "Q: %s " % self.question
+@python_2_unicode_compatible
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
- def __unicode__(self):
+ def __str__(self):
return "Choice: %s in poll %s" % (self.choice, self.poll)
# A set of models with an inner one pointing to two outer ones.
diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py
index 5d32bf03f8..38b801f40e 100644
--- a/tests/regressiontests/one_to_one_regress/models.py
+++ b/tests/regressiontests/one_to_one_regress/models.py
@@ -1,39 +1,44 @@
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
- def __unicode__(self):
+ def __str__(self):
return "%s the place" % self.name
+@python_2_unicode_compatible
class Restaurant(models.Model):
place = models.OneToOneField(Place)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the restaurant" % self.place.name
+@python_2_unicode_compatible
class Bar(models.Model):
place = models.OneToOneField(Place)
serves_cocktails = models.BooleanField()
- def __unicode__(self):
+ def __str__(self):
return "%s the bar" % self.place.name
class UndergroundBar(models.Model):
place = models.OneToOneField(Place, null=True)
serves_cocktails = models.BooleanField()
+@python_2_unicode_compatible
class Favorites(models.Model):
name = models.CharField(max_length = 50)
restaurants = models.ManyToManyField(Restaurant)
- def __unicode__(self):
+ def __str__(self):
return "Favorites for %s" % self.name
class Target(models.Model):
diff --git a/tests/regressiontests/pagination_regress/tests.py b/tests/regressiontests/pagination_regress/tests.py
index 59ac41e062..e98352e006 100644
--- a/tests/regressiontests/pagination_regress/tests.py
+++ b/tests/regressiontests/pagination_regress/tests.py
@@ -17,14 +17,16 @@ class PaginatorTests(TestCase):
paginator = Paginator(*params)
self.check_attribute('count', paginator, count, params)
self.check_attribute('num_pages', paginator, num_pages, params)
- self.check_attribute('page_range', paginator, page_range, params)
+ self.check_attribute('page_range', paginator, page_range, params, coerce=list)
- def check_attribute(self, name, paginator, expected, params):
+ def check_attribute(self, name, paginator, expected, params, coerce=None):
"""
Helper method that checks a single attribute and gives a nice error
message upon test failure.
"""
got = getattr(paginator, name)
+ if coerce is not None:
+ got = coerce(got)
self.assertEqual(expected, got,
"For '%s', expected %s but got %s. Paginator parameters were: %s"
% (name, expected, got, params))
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index 6328776e91..f0178a0256 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -7,6 +7,7 @@ import threading
from django.db import models
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class DumbCategory(models.Model):
@@ -19,6 +20,7 @@ class ProxyCategory(DumbCategory):
class NamedCategory(DumbCategory):
name = models.CharField(max_length=10)
+@python_2_unicode_compatible
class Tag(models.Model):
name = models.CharField(max_length=10)
parent = models.ForeignKey('self', blank=True, null=True,
@@ -28,9 +30,10 @@ class Tag(models.Model):
class Meta:
ordering = ['name']
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Note(models.Model):
note = models.CharField(max_length=100)
misc = models.CharField(max_length=10)
@@ -38,7 +41,7 @@ class Note(models.Model):
class Meta:
ordering = ['note']
- def __unicode__(self):
+ def __str__(self):
return self.note
def __init__(self, *args, **kwargs):
@@ -48,14 +51,16 @@ class Note(models.Model):
# that use objects of that type as an argument.
self.lock = threading.Lock()
+@python_2_unicode_compatible
class Annotation(models.Model):
name = models.CharField(max_length=10)
tag = models.ForeignKey(Tag)
notes = models.ManyToManyField(Note)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class ExtraInfo(models.Model):
info = models.CharField(max_length=100)
note = models.ForeignKey(Note)
@@ -63,9 +68,10 @@ class ExtraInfo(models.Model):
class Meta:
ordering = ['info']
- def __unicode__(self):
+ def __str__(self):
return self.info
+@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=10)
num = models.IntegerField(unique=True)
@@ -74,9 +80,10 @@ class Author(models.Model):
class Meta:
ordering = ['name']
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Item(models.Model):
name = models.CharField(max_length=10)
created = models.DateTimeField()
@@ -88,16 +95,18 @@ class Item(models.Model):
class Meta:
ordering = ['-note', 'name']
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Report(models.Model):
name = models.CharField(max_length=10)
creator = models.ForeignKey(Author, to_field='num', null=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Ranking(models.Model):
rank = models.IntegerField()
author = models.ForeignKey(Author)
@@ -106,9 +115,10 @@ class Ranking(models.Model):
# A complex ordering specification. Should stress the system a bit.
ordering = ('author__extra__note', 'author__name', 'rank')
- def __unicode__(self):
+ def __str__(self):
return '%d: %s' % (self.rank, self.author.name)
+@python_2_unicode_compatible
class Cover(models.Model):
title = models.CharField(max_length=50)
item = models.ForeignKey(Item)
@@ -116,13 +126,14 @@ class Cover(models.Model):
class Meta:
ordering = ['item']
- def __unicode__(self):
+ def __str__(self):
return self.title
+@python_2_unicode_compatible
class Number(models.Model):
num = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return six.text_type(self.num)
# Symmetrical m2m field with a normal field using the reverse accesor name
@@ -168,6 +179,7 @@ class CustomManager(models.Manager):
qs = super(CustomManager, self).get_query_set()
return qs.filter(public=True, tag__name='t1')
+@python_2_unicode_compatible
class ManagedModel(models.Model):
data = models.CharField(max_length=10)
tag = models.ForeignKey(Tag)
@@ -176,7 +188,7 @@ class ManagedModel(models.Model):
objects = CustomManager()
normal_manager = models.Manager()
- def __unicode__(self):
+ def __str__(self):
return self.data
# An inter-related setup with multiple paths from Child to Detail.
@@ -211,11 +223,12 @@ class Related(models.Model):
# An inter-related setup with a model subclass that has a nullable
# path to another model, and a return path from that model.
+@python_2_unicode_compatible
class Celebrity(models.Model):
name = models.CharField("Name", max_length=20)
greatest_fan = models.ForeignKey("Fan", null=True, unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
class TvChef(Celebrity):
@@ -225,10 +238,11 @@ class Fan(models.Model):
fan_of = models.ForeignKey(Celebrity)
# Multiple foreign keys
+@python_2_unicode_compatible
class LeafA(models.Model):
data = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return self.data
class LeafB(models.Model):
@@ -238,11 +252,12 @@ class Join(models.Model):
a = models.ForeignKey(LeafA)
b = models.ForeignKey(LeafB)
+@python_2_unicode_compatible
class ReservedName(models.Model):
name = models.CharField(max_length=20)
order = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.name
# A simpler shared-foreign-key setup that can expose some problems.
@@ -256,13 +271,14 @@ class PointerB(models.Model):
connection = models.ForeignKey(SharedConnection)
# Multi-layer ordering
+@python_2_unicode_compatible
class SingleObject(models.Model):
name = models.CharField(max_length=10)
class Meta:
ordering = ['name']
- def __unicode__(self):
+ def __str__(self):
return self.name
class RelatedObject(models.Model):
@@ -271,6 +287,7 @@ class RelatedObject(models.Model):
class Meta:
ordering = ['single']
+@python_2_unicode_compatible
class Plaything(models.Model):
name = models.CharField(max_length=10)
others = models.ForeignKey(RelatedObject, null=True)
@@ -278,79 +295,89 @@ class Plaything(models.Model):
class Meta:
ordering = ['others']
- def __unicode__(self):
+ def __str__(self):
return self.name
class Article(models.Model):
name = models.CharField(max_length=20)
created = models.DateTimeField()
+@python_2_unicode_compatible
class Food(models.Model):
name = models.CharField(max_length=20, unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Eaten(models.Model):
food = models.ForeignKey(Food, to_field="name")
meal = models.CharField(max_length=20)
- def __unicode__(self):
+ def __str__(self):
return "%s at %s" % (self.food, self.meal)
+@python_2_unicode_compatible
class Node(models.Model):
num = models.IntegerField(unique=True)
parent = models.ForeignKey("self", to_field="num", null=True)
- def __unicode__(self):
+ def __str__(self):
return "%s" % self.num
# Bug #12252
+@python_2_unicode_compatible
class ObjectA(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class ObjectB(models.Model):
name = models.CharField(max_length=50)
objecta = models.ForeignKey(ObjectA)
num = models.PositiveSmallIntegerField()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class ObjectC(models.Model):
name = models.CharField(max_length=50)
objecta = models.ForeignKey(ObjectA)
objectb = models.ForeignKey(ObjectB)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class SimpleCategory(models.Model):
name = models.CharField(max_length=15)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class SpecialCategory(SimpleCategory):
special_name = models.CharField(max_length=15)
- def __unicode__(self):
+ def __str__(self):
return self.name + " " + self.special_name
+@python_2_unicode_compatible
class CategoryItem(models.Model):
category = models.ForeignKey(SimpleCategory)
- def __unicode__(self):
+ def __str__(self):
return "category item: " + str(self.category)
+@python_2_unicode_compatible
class OneToOneCategory(models.Model):
new_name = models.CharField(max_length=15)
category = models.OneToOneField(SimpleCategory)
- def __unicode__(self):
+ def __str__(self):
return "one2one " + self.new_name
class NullableName(models.Model):
@@ -358,3 +385,18 @@ class NullableName(models.Model):
class Meta:
ordering = ['id']
+
+class ModelD(models.Model):
+ name = models.TextField()
+
+class ModelC(models.Model):
+ name = models.TextField()
+
+class ModelB(models.Model):
+ name = models.TextField()
+ c = models.ForeignKey(ModelC)
+
+class ModelA(models.Model):
+ name = models.TextField()
+ b = models.ForeignKey(ModelB, null=True)
+ d = models.ForeignKey(ModelD)
diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
index 1582993dfc..005aa9650b 100644
--- a/tests/regressiontests/queries/tests.py
+++ b/tests/regressiontests/queries/tests.py
@@ -23,7 +23,7 @@ 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)
+ SingleObject, RelatedObject, ModelA, ModelD)
class BaseQuerysetTest(TestCase):
@@ -806,7 +806,7 @@ class Queries1Tests(BaseQuerysetTest):
qs = Tag.objects.values_list('id', flat=True).order_by('id')
qs.query.bump_prefix()
first = qs[0]
- self.assertEqual(list(qs), range(first, first+5))
+ self.assertEqual(list(qs), list(range(first, first+5)))
def test_ticket8439(self):
# Complex combinations of conjunctions, disjunctions and nullable
@@ -1272,8 +1272,8 @@ class Queries5Tests(TestCase):
# them in a values() query.
dicts = qs.values('id', 'rank').order_by('id')
self.assertEqual(
- [d.items()[1] for d in dicts],
- [('rank', 2), ('rank', 1), ('rank', 3)]
+ [d['rank'] for d in dicts],
+ [2, 1, 3]
)
def test_ticket7256(self):
@@ -2043,65 +2043,88 @@ class WhereNodeTest(TestCase):
def test_empty_full_handling_conjunction(self):
qn = connection.ops.quote_name
w = WhereNode(children=[EverythingNode()])
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w.negate()
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w = WhereNode(children=[NothingNode()])
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w.negate()
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w = WhereNode(children=[EverythingNode(), EverythingNode()])
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w.negate()
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w = WhereNode(children=[EverythingNode(), self.DummyNode()])
- self.assertEquals(w.as_sql(qn, connection), ('dummy', []))
+ self.assertEqual(w.as_sql(qn, connection), ('dummy', []))
w = WhereNode(children=[self.DummyNode(), self.DummyNode()])
- self.assertEquals(w.as_sql(qn, connection), ('(dummy AND dummy)', []))
+ self.assertEqual(w.as_sql(qn, connection), ('(dummy AND dummy)', []))
w.negate()
- self.assertEquals(w.as_sql(qn, connection), ('NOT (dummy AND dummy)', []))
+ self.assertEqual(w.as_sql(qn, connection), ('NOT (dummy AND dummy)', []))
w = WhereNode(children=[NothingNode(), self.DummyNode()])
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w.negate()
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
def test_empty_full_handling_disjunction(self):
qn = connection.ops.quote_name
w = WhereNode(children=[EverythingNode()], connector='OR')
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w.negate()
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w = WhereNode(children=[NothingNode()], connector='OR')
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w.negate()
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w = WhereNode(children=[EverythingNode(), EverythingNode()], connector='OR')
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w.negate()
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w = WhereNode(children=[EverythingNode(), self.DummyNode()], connector='OR')
- self.assertEquals(w.as_sql(qn, connection), ('', []))
+ self.assertEqual(w.as_sql(qn, connection), ('', []))
w.negate()
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
w = WhereNode(children=[self.DummyNode(), self.DummyNode()], connector='OR')
- self.assertEquals(w.as_sql(qn, connection), ('(dummy OR dummy)', []))
+ self.assertEqual(w.as_sql(qn, connection), ('(dummy OR dummy)', []))
w.negate()
- self.assertEquals(w.as_sql(qn, connection), ('NOT (dummy OR dummy)', []))
+ self.assertEqual(w.as_sql(qn, connection), ('NOT (dummy OR dummy)', []))
w = WhereNode(children=[NothingNode(), self.DummyNode()], connector='OR')
- self.assertEquals(w.as_sql(qn, connection), ('dummy', []))
+ self.assertEqual(w.as_sql(qn, connection), ('dummy', []))
w.negate()
- self.assertEquals(w.as_sql(qn, connection), ('NOT (dummy)', []))
+ self.assertEqual(w.as_sql(qn, connection), ('NOT (dummy)', []))
def test_empty_nodes(self):
qn = connection.ops.quote_name
empty_w = WhereNode()
w = WhereNode(children=[empty_w, empty_w])
- self.assertEquals(w.as_sql(qn, connection), (None, []))
+ self.assertEqual(w.as_sql(qn, connection), (None, []))
w.negate()
- self.assertEquals(w.as_sql(qn, connection), (None, []))
+ self.assertEqual(w.as_sql(qn, connection), (None, []))
w.connector = 'OR'
- self.assertEquals(w.as_sql(qn, connection), (None, []))
+ self.assertEqual(w.as_sql(qn, connection), (None, []))
w.negate()
- self.assertEquals(w.as_sql(qn, connection), (None, []))
+ self.assertEqual(w.as_sql(qn, connection), (None, []))
w = WhereNode(children=[empty_w, NothingNode()], connector='OR')
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
+
+class NullJoinPromotionOrTest(TestCase):
+ def setUp(self):
+ d = ModelD.objects.create(name='foo')
+ ModelA.objects.create(name='bar', d=d)
+
+ def test_ticket_17886(self):
+ # The first Q-object is generating the match, the rest of the filters
+ # should not remove the match even if they do not match anything. The
+ # problem here was that b__name generates a LOUTER JOIN, then
+ # b__c__name generates join to c, which the ORM tried to promote but
+ # failed as that join isn't nullable.
+ q_obj = (
+ Q(d__name='foo')|
+ Q(b__name='foo')|
+ Q(b__c__name='foo')
+ )
+ qset = ModelA.objects.filter(q_obj)
+ self.assertEqual(len(qset), 1)
+ # 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)
diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
index f192459246..f9e1112b2e 100644
--- a/tests/regressiontests/requests/tests.py
+++ b/tests/regressiontests/requests/tests.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import time
import warnings
from datetime import datetime, timedelta
+from io import BytesIO
from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest, LimitedStream
@@ -10,17 +11,16 @@ from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_r
from django.test.utils import str_prefix
from django.utils import unittest
from django.utils.http import cookie_date
-from django.utils.six import StringIO
from django.utils.timezone import utc
class RequestsTests(unittest.TestCase):
def test_httprequest(self):
request = HttpRequest()
- self.assertEqual(request.GET.keys(), [])
- self.assertEqual(request.POST.keys(), [])
- self.assertEqual(request.COOKIES.keys(), [])
- self.assertEqual(request.META.keys(), [])
+ self.assertEqual(list(request.GET.keys()), [])
+ self.assertEqual(list(request.POST.keys()), [])
+ self.assertEqual(list(request.COOKIES.keys()), [])
+ self.assertEqual(list(request.META.keys()), [])
def test_httprequest_repr(self):
request = HttpRequest()
@@ -35,17 +35,17 @@ class RequestsTests(unittest.TestCase):
str_prefix("<HttpRequest\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(self):
- request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus', 'wsgi.input': StringIO('')})
- self.assertEqual(request.GET.keys(), [])
- self.assertEqual(request.POST.keys(), [])
- self.assertEqual(request.COOKIES.keys(), [])
+ request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus', 'wsgi.input': BytesIO(b'')})
+ self.assertEqual(list(request.GET.keys()), [])
+ self.assertEqual(list(request.POST.keys()), [])
+ self.assertEqual(list(request.COOKIES.keys()), [])
self.assertEqual(set(request.META.keys()), set(['PATH_INFO', 'REQUEST_METHOD', 'SCRIPT_NAME', 'wsgi.input']))
self.assertEqual(request.META['PATH_INFO'], 'bogus')
self.assertEqual(request.META['REQUEST_METHOD'], 'bogus')
self.assertEqual(request.META['SCRIPT_NAME'], '')
def test_wsgirequest_repr(self):
- request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': StringIO('')})
+ request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')})
request.GET = {'get-key': 'get-value'}
request.POST = {'post-key': 'post-value'}
request.COOKIES = {'post-key': 'post-value'}
@@ -207,26 +207,26 @@ class RequestsTests(unittest.TestCase):
def test_limited_stream(self):
# Read all of a limited stream
- stream = LimitedStream(StringIO(b'test'), 2)
+ stream = LimitedStream(BytesIO(b'test'), 2)
self.assertEqual(stream.read(), b'te')
# Reading again returns nothing.
self.assertEqual(stream.read(), b'')
# Read a number of characters greater than the stream has to offer
- stream = LimitedStream(StringIO(b'test'), 2)
+ stream = LimitedStream(BytesIO(b'test'), 2)
self.assertEqual(stream.read(5), b'te')
# Reading again returns nothing.
self.assertEqual(stream.readline(5), b'')
# Read sequentially from a stream
- stream = LimitedStream(StringIO(b'12345678'), 8)
+ stream = LimitedStream(BytesIO(b'12345678'), 8)
self.assertEqual(stream.read(5), b'12345')
self.assertEqual(stream.read(5), b'678')
# Reading again returns nothing.
self.assertEqual(stream.readline(5), b'')
# Read lines from a stream
- stream = LimitedStream(StringIO(b'1234\n5678\nabcd\nefgh\nijkl'), 24)
+ stream = LimitedStream(BytesIO(b'1234\n5678\nabcd\nefgh\nijkl'), 24)
# Read a full line, unconditionally
self.assertEqual(stream.readline(), b'1234\n')
# Read a number of characters less than a line
@@ -246,7 +246,7 @@ class RequestsTests(unittest.TestCase):
# If a stream contains a newline, but the provided length
# is less than the number of provided characters, the newline
# doesn't reset the available character count
- stream = LimitedStream(StringIO(b'1234\nabcdef'), 9)
+ stream = LimitedStream(BytesIO(b'1234\nabcdef'), 9)
self.assertEqual(stream.readline(10), b'1234\n')
self.assertEqual(stream.readline(3), b'abc')
# Now expire the available characters
@@ -255,7 +255,7 @@ class RequestsTests(unittest.TestCase):
self.assertEqual(stream.readline(2), b'')
# Same test, but with read, not readline.
- stream = LimitedStream(StringIO(b'1234\nabcdef'), 9)
+ stream = LimitedStream(BytesIO(b'1234\nabcdef'), 9)
self.assertEqual(stream.read(6), b'1234\na')
self.assertEqual(stream.read(2), b'bc')
self.assertEqual(stream.read(2), b'd')
@@ -266,7 +266,7 @@ class RequestsTests(unittest.TestCase):
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.read(), b'name=value')
def test_read_after_value(self):
@@ -277,7 +277,7 @@ class RequestsTests(unittest.TestCase):
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.POST, {'name': ['value']})
self.assertEqual(request.body, b'name=value')
self.assertEqual(request.read(), b'name=value')
@@ -290,7 +290,7 @@ class RequestsTests(unittest.TestCase):
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.read(2), b'na')
self.assertRaises(Exception, lambda: request.body)
self.assertEqual(request.POST, {})
@@ -312,7 +312,7 @@ class RequestsTests(unittest.TestCase):
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.POST, {'name': ['value']})
self.assertRaises(Exception, lambda: request.body)
@@ -334,14 +334,14 @@ class RequestsTests(unittest.TestCase):
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
'CONTENT_LENGTH': 0,
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
self.assertEqual(request.POST, {})
def test_read_by_lines(self):
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
self.assertEqual(list(request), [b'name=value'])
def test_POST_after_body_read(self):
@@ -351,7 +351,7 @@ class RequestsTests(unittest.TestCase):
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
raw_data = request.body
self.assertEqual(request.POST, {'name': ['value']})
@@ -363,7 +363,7 @@ class RequestsTests(unittest.TestCase):
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
raw_data = request.body
self.assertEqual(request.read(1), b'n')
self.assertEqual(request.POST, {'name': ['value']})
@@ -383,7 +383,7 @@ class RequestsTests(unittest.TestCase):
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)})
+ 'wsgi.input': BytesIO(payload)})
raw_data = request.body
# Consume enough data to mess up the parsing:
self.assertEqual(request.read(13), b'--boundary\r\nC')
@@ -397,7 +397,7 @@ class RequestsTests(unittest.TestCase):
request = WSGIRequest({
'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': StringIO(payload)
+ 'wsgi.input': BytesIO(payload)
})
with warnings.catch_warnings(record=True):
@@ -408,14 +408,14 @@ class RequestsTests(unittest.TestCase):
If wsgi.input.read() raises an exception while trying to read() the
POST, the exception should be identifiable (not a generic IOError).
"""
- class ExplodingStringIO(StringIO):
+ class ExplodingBytesIO(BytesIO):
def read(self, len=0):
raise IOError("kaboom!")
payload = b'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload),
- 'wsgi.input': ExplodingStringIO(payload)})
+ 'wsgi.input': ExplodingBytesIO(payload)})
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
diff --git a/tests/regressiontests/select_related_onetoone/models.py b/tests/regressiontests/select_related_onetoone/models.py
index 3d6da9b4c5..3284defb11 100644
--- a/tests/regressiontests/select_related_onetoone/models.py
+++ b/tests/regressiontests/select_related_onetoone/models.py
@@ -1,44 +1,50 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class User(models.Model):
username = models.CharField(max_length=100)
email = models.EmailField()
- def __unicode__(self):
+ def __str__(self):
return self.username
+@python_2_unicode_compatible
class UserProfile(models.Model):
user = models.OneToOneField(User)
city = models.CharField(max_length=100)
state = models.CharField(max_length=2)
- def __unicode__(self):
+ def __str__(self):
return "%s, %s" % (self.city, self.state)
+@python_2_unicode_compatible
class UserStatResult(models.Model):
results = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return 'UserStatResults, results = %s' % (self.results,)
+@python_2_unicode_compatible
class UserStat(models.Model):
user = models.OneToOneField(User, primary_key=True)
posts = models.IntegerField()
results = models.ForeignKey(UserStatResult)
- def __unicode__(self):
+ def __str__(self):
return 'UserStat, posts = %s' % (self.posts,)
+@python_2_unicode_compatible
class StatDetails(models.Model):
base_stats = models.OneToOneField(UserStat)
comments = models.IntegerField()
- def __unicode__(self):
+ def __str__(self):
return 'StatDetails, comments = %s' % (self.comments,)
diff --git a/tests/regressiontests/select_related_regress/models.py b/tests/regressiontests/select_related_regress/models.py
index 1af9ff4bca..a291a547f7 100644
--- a/tests/regressiontests/select_related_regress/models.py
+++ b/tests/regressiontests/select_related_regress/models.py
@@ -1,34 +1,39 @@
from __future__ import unicode_literals
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Building(models.Model):
name = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return "Building: %s" % self.name
+@python_2_unicode_compatible
class Device(models.Model):
building = models.ForeignKey('Building')
name = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return "device '%s' in building %s" % (self.name, self.building)
+@python_2_unicode_compatible
class Port(models.Model):
device = models.ForeignKey('Device')
port_number = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return "%s/%s" % (self.device.name, self.port_number)
+@python_2_unicode_compatible
class Connection(models.Model):
start = models.ForeignKey(Port, related_name='connection_start',
unique=True)
end = models.ForeignKey(Port, related_name='connection_end', unique=True)
- def __unicode__(self):
+ def __str__(self):
return "%s to %s" % (self.start, self.end)
# Another non-tree hierarchy that exercises code paths similar to the above
@@ -72,18 +77,20 @@ class SpecialClient(Client):
value = models.IntegerField()
# Some model inheritance exercises
+@python_2_unicode_compatible
class Parent(models.Model):
name = models.CharField(max_length=10)
- def __unicode__(self):
+ def __str__(self):
return self.name
class Child(Parent):
value = models.IntegerField()
+@python_2_unicode_compatible
class Item(models.Model):
name = models.CharField(max_length=10)
child = models.ForeignKey(Child, null=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index 4e73be015c..f4b3adaa5e 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -10,7 +10,6 @@ from __future__ import absolute_import, unicode_literals
import datetime
import decimal
-from io import BytesIO
try:
import yaml
@@ -23,6 +22,7 @@ from django.core.serializers.base import DeserializationError
from django.db import connection, models
from django.http import HttpResponse
from django.test import TestCase
+from django.utils import six
from django.utils.functional import curry
from django.utils.unittest import skipUnless
@@ -502,17 +502,17 @@ def streamTest(format, self):
obj.save_base(raw=True)
# Serialize the test database to a stream
- for stream in (BytesIO(), HttpResponse()):
+ for stream in (six.StringIO(), HttpResponse()):
serializers.serialize(format, [obj], indent=2, stream=stream)
# Serialize normally for a comparison
string_data = serializers.serialize(format, [obj], indent=2)
# Check that the two are the same
- if isinstance(stream, BytesIO):
+ if isinstance(stream, six.StringIO):
self.assertEqual(string_data, stream.getvalue())
else:
- self.assertEqual(string_data, stream.content)
+ self.assertEqual(string_data, stream.content.decode('utf-8'))
stream.close()
for format in serializers.get_serializer_formats():
diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
index b98b4b73c2..c90c785a6e 100644
--- a/tests/regressiontests/servers/tests.py
+++ b/tests/regressiontests/servers/tests.py
@@ -144,7 +144,7 @@ class LiveServerDatabase(LiveServerBase):
Refs #2879.
"""
f = self.urlopen('/model_view/')
- self.assertEqual(f.read().splitlines(), ['jane', 'robert'])
+ self.assertEqual(f.read().splitlines(), [b'jane', b'robert'])
def test_database_writes(self):
"""
diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py
index ffcb79eb09..7225fb03ef 100644
--- a/tests/regressiontests/settings_tests/tests.py
+++ b/tests/regressiontests/settings_tests/tests.py
@@ -148,6 +148,16 @@ class SettingsTests(TestCase):
def test_settings_delete_wrapped(self):
self.assertRaises(TypeError, delattr, settings, '_wrapped')
+ def test_override_settings_delete(self):
+ """
+ Allow deletion of a setting in an overriden settings set (#18824)
+ """
+ previous_i18n = settings.USE_I18N
+ with self.settings(USE_I18N=False):
+ del settings.USE_I18N
+ self.assertRaises(AttributeError, getattr, settings, 'USE_I18N')
+ self.assertEqual(settings.USE_I18N, previous_i18n)
+
def test_allowed_include_roots_string(self):
"""
ALLOWED_INCLUDE_ROOTS is not allowed to be incorrectly set to a string
diff --git a/tests/regressiontests/signals_regress/models.py b/tests/regressiontests/signals_regress/models.py
index bf64f69e8c..829314c06c 100644
--- a/tests/regressiontests/signals_regress/models.py
+++ b/tests/regressiontests/signals_regress/models.py
@@ -1,15 +1,18 @@
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 __unicode__(self):
+ 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 __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/tests/regressiontests/signing/tests.py b/tests/regressiontests/signing/tests.py
index 2368405060..05c8b3dc74 100644
--- a/tests/regressiontests/signing/tests.py
+++ b/tests/regressiontests/signing/tests.py
@@ -4,7 +4,8 @@ import time
from django.core import signing
from django.test import TestCase
-from django.utils.encoding import force_text
+from django.utils.encoding import force_str
+from django.utils import six
class TestSigner(TestCase):
@@ -21,7 +22,7 @@ class TestSigner(TestCase):
self.assertEqual(
signer.signature(s),
signing.base64_hmac(signer.salt + 'signer', s,
- 'predictable-secret')
+ 'predictable-secret').decode()
)
self.assertNotEqual(signer.signature(s), signer2.signature(s))
@@ -31,7 +32,8 @@ class TestSigner(TestCase):
self.assertEqual(
signer.signature('hello'),
signing.base64_hmac('extra-salt' + 'signer',
- 'hello', 'predictable-secret'))
+ 'hello', 'predictable-secret').decode()
+ )
self.assertNotEqual(
signing.Signer('predictable-secret', salt='one').signature('hello'),
signing.Signer('predictable-secret', salt='two').signature('hello'))
@@ -39,17 +41,20 @@ class TestSigner(TestCase):
def test_sign_unsign(self):
"sign/unsign should be reversible"
signer = signing.Signer('predictable-secret')
- examples = (
+ examples = [
'q;wjmbk;wkmb',
'3098247529087',
'3098247:529:087:',
'jkw osanteuh ,rcuh nthu aou oauh ,ud du',
'\u2019',
- )
+ ]
+ if not six.PY3:
+ examples.append(b'a byte string')
for example in examples:
- self.assertNotEqual(
- force_text(example), force_text(signer.sign(example)))
- self.assertEqual(example, signer.unsign(signer.sign(example)))
+ signed = signer.sign(example)
+ self.assertIsInstance(signed, str)
+ self.assertNotEqual(force_str(example), signed)
+ self.assertEqual(example, signer.unsign(signed))
def unsign_detects_tampering(self):
"unsign should raise an exception if the value has been tampered with"
@@ -69,15 +74,18 @@ class TestSigner(TestCase):
def test_dumps_loads(self):
"dumps and loads be reversible for any JSON serializable object"
- objects = (
+ objects = [
['a', 'list'],
- b'a string',
'a unicode string \u2019',
{'a': 'dictionary'},
- )
+ ]
+ if not six.PY3:
+ objects.append(b'a byte string')
for o in objects:
self.assertNotEqual(o, signing.dumps(o))
self.assertEqual(o, signing.loads(signing.dumps(o)))
+ self.assertNotEqual(o, signing.dumps(o, compress=True))
+ self.assertEqual(o, signing.loads(signing.dumps(o, compress=True)))
def test_decode_detects_tampering(self):
"loads should raise exception for tampered objects"
diff --git a/tests/regressiontests/sites_framework/models.py b/tests/regressiontests/sites_framework/models.py
index 9ecc3e6660..55c4f4992e 100644
--- a/tests/regressiontests/sites_framework/models.py
+++ b/tests/regressiontests/sites_framework/models.py
@@ -1,7 +1,9 @@
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class AbstractArticle(models.Model):
title = models.CharField(max_length=50)
@@ -11,7 +13,7 @@ class AbstractArticle(models.Model):
class Meta:
abstract = True
- def __unicode__(self):
+ def __str__(self):
return self.title
class SyndicatedArticle(AbstractArticle):
diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
index 19951f100b..078788a6a9 100644
--- a/tests/regressiontests/staticfiles_tests/tests.py
+++ b/tests/regressiontests/staticfiles_tests/tests.py
@@ -7,7 +7,6 @@ import posixpath
import shutil
import sys
import tempfile
-from io import BytesIO
from django.template import loader, Context
from django.conf import settings
@@ -194,19 +193,18 @@ class TestFindStatic(CollectionTestCase, TestDefaults):
Test ``findstatic`` management command.
"""
def _get_file(self, filepath):
- out = BytesIO()
+ out = six.StringIO()
call_command('findstatic', filepath, all=False, verbosity=0, stdout=out)
out.seek(0)
lines = [l.strip() for l in out.readlines()]
- contents = codecs.open(
- smart_text(lines[1].strip()), "r", "utf-8").read()
- return contents
+ with codecs.open(smart_text(lines[1].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.
"""
- out = BytesIO()
+ out = six.StringIO()
call_command('findstatic', 'test/file.txt', verbosity=0, stdout=out)
out.seek(0)
lines = [l.strip() for l in out.readlines()]
@@ -528,11 +526,11 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
"""
Handle cache key creation correctly, see #17861.
"""
- name = b"/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/" + chr(22) + chr(180)
+ name = "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/" + "\x16" + "\xb4"
cache_key = storage.staticfiles_storage.cache_key(name)
cache_validator = BaseCache({})
cache_validator.validate_key(cache_key)
- self.assertEqual(cache_key, 'staticfiles:e95bbc36387084582df2a70750d7b351')
+ self.assertEqual(cache_key, 'staticfiles:821ea71ef36f95b3922a77f7364670e7')
# we set DEBUG to False here since the template tag wouldn't work otherwise
@@ -570,8 +568,8 @@ class TestCollectionSimpleCachedStorage(BaseCollectionTestCase,
self.assertEqual(relpath, "cached/styles.deploy12345.css")
with storage.staticfiles_storage.open(relpath) as relfile:
content = relfile.read()
- self.assertNotIn("cached/other.css", content)
- self.assertIn("other.deploy12345.css", content)
+ self.assertNotIn(b"cached/other.css", content)
+ self.assertIn(b"other.deploy12345.css", content)
if sys.platform != 'win32':
diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py
index 53687a22cb..a2d64cd0b2 100644
--- a/tests/regressiontests/string_lookup/models.py
+++ b/tests/regressiontests/string_lookup/models.py
@@ -1,42 +1,51 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Foo(models.Model):
name = models.CharField(max_length=50)
friend = models.CharField(max_length=50, blank=True)
- def __unicode__(self):
+ def __str__(self):
return "Foo %s" % self.name
+@python_2_unicode_compatible
class Bar(models.Model):
name = models.CharField(max_length=50)
normal = models.ForeignKey(Foo, related_name='normal_foo')
fwd = models.ForeignKey("Whiz")
back = models.ForeignKey("Foo")
- def __unicode__(self):
+ def __str__(self):
return "Bar %s" % self.place.name
+@python_2_unicode_compatible
class Whiz(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return "Whiz %s" % self.name
+@python_2_unicode_compatible
class Child(models.Model):
parent = models.OneToOneField('Base')
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return "Child %s" % self.name
+@python_2_unicode_compatible
class Base(models.Model):
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return "Base %s" % self.name
+@python_2_unicode_compatible
class Article(models.Model):
name = models.CharField(max_length=50)
text = models.TextField()
diff --git a/tests/regressiontests/syndication/models.py b/tests/regressiontests/syndication/models.py
index a2c504e57f..10b3fe3a0c 100644
--- a/tests/regressiontests/syndication/models.py
+++ b/tests/regressiontests/syndication/models.py
@@ -1,6 +1,8 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Entry(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField()
@@ -8,17 +10,18 @@ class Entry(models.Model):
class Meta:
ordering = ('date',)
- def __unicode__(self):
+ def __str__(self):
return self.title
def get_absolute_url(self):
return "/blog/%s/" % self.pk
+@python_2_unicode_compatible
class Article(models.Model):
title = models.CharField(max_length=200)
entry = models.ForeignKey(Entry)
- def __unicode__(self):
+ def __str__(self):
return self.title
diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py
index fd570700af..8b4aebbf22 100644
--- a/tests/regressiontests/templates/filters.py
+++ b/tests/regressiontests/templates/filters.py
@@ -13,14 +13,17 @@ from datetime import date, datetime, timedelta
from django.test.utils import str_prefix
from django.utils.tzinfo import LocalTimezone, FixedOffset
from django.utils.safestring import mark_safe
+from django.utils.encoding import python_2_unicode_compatible
# These two classes are used to test auto-escaping of __unicode__ output.
+@python_2_unicode_compatible
class UnsafeClass:
- def __unicode__(self):
+ def __str__(self):
return 'you & me'
+@python_2_unicode_compatible
class SafeClass:
- def __unicode__(self):
+ def __str__(self):
return mark_safe('you &gt; me')
# RESULT SYNTAX --
@@ -111,7 +114,7 @@ def get_filter_tests():
# The make_list filter can destroy existing escaping, so the results are
# escaped.
'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")),
- 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, "[u&#39;&amp;&#39;]"),
+ 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, str_prefix("[%(_)s&#39;&amp;&#39;]")),
'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")),
'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")),
@@ -338,11 +341,11 @@ def get_filter_tests():
'join04': (r'{% autoescape off %}{{ a|join:" &amp; " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha &amp; beta & me'),
# Test that joining with unsafe joiners don't result in unsafe strings (#11377)
- 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha &amp; beta &amp; me'),
- 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta &amp; me'),
- 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha &amp; beta &amp; me'),
- 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta &amp; me'),
-
+ 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha &amp; beta &amp; me'),
+ 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta &amp; me'),
+ 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha &amp; beta &amp; me'),
+ 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta &amp; me'),
+
'date01': (r'{{ d|date:"m" }}', {'d': datetime(2008, 1, 1)}, '01'),
'date02': (r'{{ d|date }}', {'d': datetime(2008, 1, 1)}, 'Jan. 1, 2008'),
#Ticket 9520: Make sure |date doesn't blow up on non-dates
diff --git a/tests/regressiontests/templates/response.py b/tests/regressiontests/templates/response.py
index 3c45b7a9d4..93919b95cd 100644
--- a/tests/regressiontests/templates/response.py
+++ b/tests/regressiontests/templates/response.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import os
import pickle
import time
@@ -29,16 +31,16 @@ class SimpleTemplateResponseTest(TestCase):
def test_template_resolving(self):
response = SimpleTemplateResponse('first/test.html')
response.render()
- self.assertEqual('First template\n', response.content)
+ self.assertEqual(response.content, b'First template\n')
templates = ['foo.html', 'second/test.html', 'first/test.html']
response = SimpleTemplateResponse(templates)
response.render()
- self.assertEqual('Second template\n', response.content)
+ self.assertEqual(response.content, b'Second template\n')
response = self._response()
response.render()
- self.assertEqual(response.content, 'foo')
+ self.assertEqual(response.content, b'foo')
def test_explicit_baking(self):
# explicit baking
@@ -50,17 +52,17 @@ class SimpleTemplateResponseTest(TestCase):
def test_render(self):
# response is not re-rendered without the render call
response = self._response().render()
- self.assertEqual(response.content, 'foo')
+ self.assertEqual(response.content, b'foo')
# rebaking doesn't change the rendered content
response.template_name = Template('bar{{ baz }}')
response.render()
- self.assertEqual(response.content, 'foo')
+ self.assertEqual(response.content, b'foo')
# but rendered content can be overridden by manually
# setting content
response.content = 'bar'
- self.assertEqual(response.content, 'bar')
+ self.assertEqual(response.content, b'bar')
def test_iteration_unrendered(self):
# unrendered response raises an exception on iteration
@@ -77,7 +79,7 @@ class SimpleTemplateResponseTest(TestCase):
# iteration works for rendered responses
response = self._response().render()
res = [x for x in response]
- self.assertEqual(res, ['foo'])
+ self.assertEqual(res, [b'foo'])
def test_content_access_unrendered(self):
# unrendered response raises an exception when content is accessed
@@ -89,7 +91,7 @@ class SimpleTemplateResponseTest(TestCase):
def test_content_access_rendered(self):
# rendered response content can be accessed
response = self._response().render()
- self.assertEqual(response.content, 'foo')
+ self.assertEqual(response.content, b'foo')
def test_set_content(self):
# content can be overriden
@@ -97,23 +99,23 @@ class SimpleTemplateResponseTest(TestCase):
self.assertFalse(response.is_rendered)
response.content = 'spam'
self.assertTrue(response.is_rendered)
- self.assertEqual(response.content, 'spam')
+ self.assertEqual(response.content, b'spam')
response.content = 'baz'
- self.assertEqual(response.content, 'baz')
+ self.assertEqual(response.content, b'baz')
def test_dict_context(self):
response = self._response('{{ foo }}{{ processors }}',
{'foo': 'bar'})
self.assertEqual(response.context_data, {'foo': 'bar'})
response.render()
- self.assertEqual(response.content, 'bar')
+ self.assertEqual(response.content, b'bar')
def test_context_instance(self):
response = self._response('{{ foo }}{{ processors }}',
Context({'foo': 'bar'}))
self.assertEqual(response.context_data.__class__, Context)
response.render()
- self.assertEqual(response.content, 'bar')
+ self.assertEqual(response.content, b'bar')
def test_kwargs(self):
response = self._response(content_type = 'application/json', status=504)
@@ -140,7 +142,7 @@ class SimpleTemplateResponseTest(TestCase):
# When the content is rendered, all the callbacks are invoked, too.
response.render()
- self.assertEqual('First template\n', response.content)
+ self.assertEqual(response.content, b'First template\n')
self.assertEqual(post, ['post1','post2'])
@@ -202,17 +204,17 @@ class TemplateResponseTest(TestCase):
def test_render(self):
response = self._response('{{ foo }}{{ processors }}').render()
- self.assertEqual(response.content, 'yes')
+ self.assertEqual(response.content, b'yes')
def test_render_with_requestcontext(self):
response = self._response('{{ foo }}{{ processors }}',
{'foo': 'bar'}).render()
- self.assertEqual(response.content, 'baryes')
+ self.assertEqual(response.content, b'baryes')
def test_render_with_context(self):
response = self._response('{{ foo }}{{ processors }}',
Context({'foo': 'bar'})).render()
- self.assertEqual(response.content, 'bar')
+ self.assertEqual(response.content, b'bar')
def test_kwargs(self):
response = self._response(content_type = 'application/json',
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index edbb21b6bd..41f40e7467 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -27,6 +27,7 @@ from django.test import RequestFactory
from django.test.utils import (setup_test_template_loader,
restore_template_loaders, override_settings)
from django.utils import unittest
+from django.utils.encoding import python_2_unicode_compatible
from django.utils.formats import date_format
from django.utils.translation import activate, deactivate, ugettext as _
from django.utils.safestring import mark_safe
@@ -46,7 +47,7 @@ from .response import (TemplateResponseTest, CacheMiddlewareTest,
try:
from .loaders import RenderToStringTest, EggLoaderTest
except ImportError as e:
- if "pkg_resources" in e.message:
+ if "pkg_resources" in e.args[0]:
pass # If setuptools isn't installed, that's fine. Just move on.
else:
raise
@@ -148,10 +149,11 @@ class SilentAttrClass(object):
raise SomeException
b = property(b)
+@python_2_unicode_compatible
class UTF8Class:
- "Class whose __str__ returns non-ASCII data"
+ "Class whose __str__ returns non-ASCII data on Python 2"
def __str__(self):
- return 'ŠĐĆŽćžšđ'.encode('utf-8')
+ return 'ŠĐĆŽćžšđ'
class Templates(unittest.TestCase):
def setUp(self):
@@ -408,8 +410,7 @@ class Templates(unittest.TestCase):
)
failures = []
- tests = template_tests.items()
- tests.sort()
+ tests = sorted(template_tests.items())
# Turn TEMPLATE_DEBUG off, because tests assume that.
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
@@ -418,7 +419,7 @@ class Templates(unittest.TestCase):
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
expected_invalid_str = 'INVALID'
- #Set ALLOWED_INCLUDE_ROOTS so that ssi works.
+ # Set ALLOWED_INCLUDE_ROOTS so that ssi works.
old_allowed_include_roots = settings.ALLOWED_INCLUDE_ROOTS
settings.ALLOWED_INCLUDE_ROOTS = (
os.path.dirname(os.path.abspath(__file__)),
@@ -1452,8 +1453,9 @@ class Templates(unittest.TestCase):
'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
- # 62.5 should round to 63
- 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
+ # 62.5 should round to 63 on Python 2 and 62 on Python 3
+ # See http://docs.python.org/py3k/whatsnew/3.0.html
+ 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '62' if six.PY3 else '63'),
# 71.4 should round to 71
'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
@@ -1466,6 +1468,14 @@ class Templates(unittest.TestCase):
# #10043: widthratio should allow max_width to be a variable
'widthratio11': ('{% widthratio a b c %}', {'a':50,'b':100, 'c': 100}, '50'),
+ # #18739: widthratio should handle None args consistently with non-numerics
+ 'widthratio12a': ('{% widthratio a b c %}', {'a':'a','b':100,'c':100}, ''),
+ 'widthratio12b': ('{% widthratio a b c %}', {'a':None,'b':100,'c':100}, ''),
+ 'widthratio13a': ('{% widthratio a b c %}', {'a':0,'b':'b','c':100}, ''),
+ 'widthratio13b': ('{% widthratio a b c %}', {'a':0,'b':None,'c':100}, ''),
+ '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),
+
### 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'),
diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/regressiontests/test_client_regress/tests.py
index 59f8f423dd..d80293b358 100644
--- a/tests/regressiontests/test_client_regress/tests.py
+++ b/tests/regressiontests/test_client_regress/tests.py
@@ -858,7 +858,7 @@ class UnicodePayloadTests(TestCase):
json = '{"english": "mountain pass"}'
response = self.client.post("/test_client_regress/parse_unicode_json/", json,
content_type="application/json")
- self.assertEqual(response.content, json)
+ self.assertEqual(response.content, json.encode())
def test_unicode_payload_utf8(self):
"A non-ASCII unicode data encoded as UTF-8 can be POSTed"
@@ -888,7 +888,7 @@ class DummyFile(object):
def __init__(self, filename):
self.name = filename
def read(self):
- return 'TEST_FILE_CONTENT'
+ return b'TEST_FILE_CONTENT'
class UploadedFileEncodingTest(TestCase):
def test_file_encoding(self):
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
index 8792a97dc0..9b0654806b 100644
--- a/tests/regressiontests/test_client_regress/views.py
+++ b/tests/regressiontests/test_client_regress/views.py
@@ -80,9 +80,7 @@ def return_json_file(request):
# This just checks that the uploaded data is JSON
obj_dict = json.loads(request.body.decode(charset))
- obj_json = json.dumps(obj_dict, encoding=charset,
- cls=DjangoJSONEncoder,
- ensure_ascii=False)
+ obj_json = json.dumps(obj_dict, cls=DjangoJSONEncoder, ensure_ascii=False)
response = HttpResponse(obj_json.encode(charset), status=200,
content_type='application/json; charset=%s' % charset)
response['Content-Disposition'] = 'attachment; filename=testfile.json'
diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
index f43a855a59..3749014372 100644
--- a/tests/regressiontests/test_utils/tests.py
+++ b/tests/regressiontests/test_utils/tests.py
@@ -5,6 +5,7 @@ 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.utils import six
from django.utils.unittest import skip
from .models import Person
@@ -495,12 +496,6 @@ __test__ = {"API_TEST": r"""
>>> from django.utils.xmlutils import SimplerXMLGenerator
>>> from django.utils.six import StringIO
->>> def produce_long():
-... return 42L
-
->>> def produce_int():
-... return 42
-
>>> def produce_json():
... return json.dumps(['foo', {'bar': ('baz', None, 1.0, 2), 'whiz': 42}])
@@ -529,14 +524,6 @@ __test__ = {"API_TEST": r"""
... xml.endElement("bar")
... return stream.getvalue()
-# Long values are normalized and are comparable to normal integers ...
->>> produce_long()
-42
-
-# ... and vice versa
->>> produce_int()
-42L
-
# JSON output is normalized for field order, so it doesn't matter
# which order json dictionary attributes are listed in output
>>> produce_json()
@@ -560,3 +547,21 @@ __test__ = {"API_TEST": r"""
'<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/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
index 500a0e0327..168892719a 100644
--- a/tests/regressiontests/urlpatterns_reverse/tests.py
+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
@@ -1,12 +1,13 @@
"""
Unit tests for reverse URL lookups.
"""
-from __future__ import absolute_import
+from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
-from django.core.urlresolvers import (reverse, resolve, NoReverseMatch,
- Resolver404, ResolverMatch, RegexURLResolver, RegexURLPattern)
+from django.core.urlresolvers import (reverse, resolve, get_callable,
+ get_resolver, NoReverseMatch, Resolver404, ResolverMatch, RegexURLResolver,
+ RegexURLPattern)
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.shortcuts import redirect
from django.test import TestCase
@@ -171,6 +172,16 @@ class URLPatternReverse(TestCase):
self.assertRaises(NoReverseMatch, reverse, None)
class ResolverTests(unittest.TestCase):
+ def test_resolver_repr(self):
+ """
+ Test repr of RegexURLResolver, especially when urlconf_name is a list
+ (#17892).
+ """
+ # Pick a resolver from a namespaced urlconf
+ resolver = get_resolver('regressiontests.urlpatterns_reverse.namespace_urls')
+ sub_resolver = resolver.namespace_dict['test-ns1'][1]
+ self.assertIn('<RegexURLPattern list>', repr(sub_resolver))
+
def test_non_regex(self):
"""
Verifies that we raise a Resolver404 if what we are resolving doesn't
@@ -405,8 +416,8 @@ class RequestURLconfTests(TestCase):
def test_urlconf(self):
response = self.client.get('/test/me/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'outer:/test/me/,'
- 'inner:/inner_urlconf/second_test/')
+ self.assertEqual(response.content, b'outer:/test/me/,'
+ b'inner:/inner_urlconf/second_test/')
response = self.client.get('/inner_urlconf/second_test/')
self.assertEqual(response.status_code, 200)
response = self.client.get('/second_test/')
@@ -422,7 +433,7 @@ class RequestURLconfTests(TestCase):
self.assertEqual(response.status_code, 404)
response = self.client.get('/second_test/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'outer:,inner:/second_test/')
+ self.assertEqual(response.content, b'outer:,inner:/second_test/')
def test_urlconf_overridden_with_null(self):
settings.MIDDLEWARE_CLASSES += (
@@ -519,3 +530,16 @@ class ErroneousViewTests(TestCase):
"""
# The regex error will be hit before NoReverseMatch can be raised
self.assertRaises(ImproperlyConfigured, reverse, 'whatever blah blah')
+
+class ViewLoadingTests(TestCase):
+ def test_view_loading(self):
+ # A missing view (identified by an AttributeError) should raise
+ # ViewDoesNotExist, ...
+ self.assertRaisesRegexp(ViewDoesNotExist, ".*View does not exist in.*",
+ get_callable,
+ 'regressiontests.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')
+
diff --git a/tests/regressiontests/urlpatterns_reverse/views_broken.py b/tests/regressiontests/urlpatterns_reverse/views_broken.py
new file mode 100644
index 0000000000..4953aab239
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/views_broken.py
@@ -0,0 +1,2 @@
+# I just raise an AttributeError to confuse the view loading mechanism
+raise AttributeError('I am here to confuse django.core.urlresolvers.get_callable')
diff --git a/tests/regressiontests/utils/archive.py b/tests/regressiontests/utils/archive.py
index 0927c624f3..5575f340f6 100644
--- a/tests/regressiontests/utils/archive.py
+++ b/tests/regressiontests/utils/archive.py
@@ -27,12 +27,14 @@ class ArchiveTester(object):
os.chdir(self.old_cwd)
def test_extract_method(self):
- Archive(self.archive).extract(self.tmpdir)
+ with Archive(self.archive) as archive:
+ archive.extract(self.tmpdir)
self.check_files(self.tmpdir)
def test_extract_method_no_to_path(self):
os.chdir(self.tmpdir)
- Archive(self.archive_path).extract()
+ with Archive(self.archive_path) as archive:
+ archive.extract()
self.check_files(self.tmpdir)
def test_extract_function(self):
diff --git a/tests/regressiontests/utils/crypto.py b/tests/regressiontests/utils/crypto.py
index 52a286cb27..4c6b722ca9 100644
--- a/tests/regressiontests/utils/crypto.py
+++ b/tests/regressiontests/utils/crypto.py
@@ -6,7 +6,17 @@ import timeit
import hashlib
from django.utils import unittest
-from django.utils.crypto import pbkdf2
+from django.utils.crypto import constant_time_compare, pbkdf2
+
+
+class TestUtilsCryptoMisc(unittest.TestCase):
+
+ def test_constant_time_compare(self):
+ # It's hard to test for constant time, just test the result.
+ self.assertTrue(constant_time_compare(b'spam', b'spam'))
+ self.assertFalse(constant_time_compare(b'spam', b'eggs'))
+ self.assertTrue(constant_time_compare('spam', 'spam'))
+ self.assertFalse(constant_time_compare('spam', 'eggs'))
class TestUtilsCryptoPBKDF2(unittest.TestCase):
diff --git a/tests/regressiontests/utils/dateparse.py b/tests/regressiontests/utils/dateparse.py
new file mode 100644
index 0000000000..1a6ca646b8
--- /dev/null
+++ b/tests/regressiontests/utils/dateparse.py
@@ -0,0 +1,44 @@
+from __future__ import unicode_literals
+
+from datetime import date, time, datetime
+
+from django.utils.dateparse import parse_date, parse_time, parse_datetime
+from django.utils import unittest
+from django.utils.tzinfo import FixedOffset
+
+
+class DateParseTests(unittest.TestCase):
+
+ def test_parse_date(self):
+ # Valid inputs
+ self.assertEqual(parse_date('2012-04-23'), date(2012, 4, 23))
+ self.assertEqual(parse_date('2012-4-9'), date(2012, 4, 9))
+ # Invalid inputs
+ self.assertEqual(parse_date('20120423'), None)
+ self.assertRaises(ValueError, parse_date, '2012-04-56')
+
+ def test_parse_time(self):
+ # Valid inputs
+ self.assertEqual(parse_time('09:15:00'), time(9, 15))
+ self.assertEqual(parse_time('10:10'), time(10, 10))
+ self.assertEqual(parse_time('10:20:30.400'), time(10, 20, 30, 400000))
+ self.assertEqual(parse_time('4:8:16'), time(4, 8, 16))
+ # Invalid inputs
+ self.assertEqual(parse_time('091500'), None)
+ self.assertRaises(ValueError, parse_time, '09:15:90')
+
+ def test_parse_datetime(self):
+ # Valid inputs
+ self.assertEqual(parse_datetime('2012-04-23T09:15:00'),
+ datetime(2012, 4, 23, 9, 15))
+ self.assertEqual(parse_datetime('2012-4-9 4:8:16'),
+ datetime(2012, 4, 9, 4, 8, 16))
+ self.assertEqual(parse_datetime('2012-04-23T09:15:00Z'),
+ datetime(2012, 4, 23, 9, 15, 0, 0, FixedOffset(0)))
+ self.assertEqual(parse_datetime('2012-4-9 4:8:16-0320'),
+ datetime(2012, 4, 9, 4, 8, 16, 0, FixedOffset(-200)))
+ self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:30'),
+ datetime(2012, 4, 23, 10, 20, 30, 400000, FixedOffset(150)))
+ # Invalid inputs
+ self.assertEqual(parse_datetime('20120423091500'), None)
+ self.assertRaises(ValueError, parse_datetime, '2012-04-56T09:15:90')
diff --git a/tests/regressiontests/utils/encoding.py b/tests/regressiontests/utils/encoding.py
new file mode 100644
index 0000000000..d191845518
--- /dev/null
+++ b/tests/regressiontests/utils/encoding.py
@@ -0,0 +1,17 @@
+# -*- encoding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.utils import unittest
+from django.utils.encoding import force_bytes
+
+
+class TestEncodingUtils(unittest.TestCase):
+ def test_force_bytes_exception(self):
+ """
+ Test that force_bytes knows how to convert to bytes an exception
+ containing non-ASCII characters in its args.
+ """
+ error_msg = "This is an exception, voilà"
+ exc = ValueError(error_msg)
+ result = force_bytes(exc)
+ self.assertEqual(result, error_msg.encode('utf-8'))
diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py
index fe40d4eaae..98df80a5e2 100644
--- a/tests/regressiontests/utils/html.py
+++ b/tests/regressiontests/utils/html.py
@@ -146,3 +146,12 @@ class TestUtilsHtml(unittest.TestCase):
)
for value, output in items:
self.check_output(f, value, output)
+
+ def test_remove_tags(self):
+ f = html.remove_tags
+ items = (
+ ("<b><i>Yes</i></b>", "b i", "Yes"),
+ ("<a>x</a> <p><b>y</b></p>", "a b", "x <p>y</p>"),
+ )
+ for value, tags, output in items:
+ self.assertEquals(f(value, tags), output)
diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py
index cbd81c7294..dffb51966c 100644
--- a/tests/regressiontests/utils/module_loading.py
+++ b/tests/regressiontests/utils/module_loading.py
@@ -109,7 +109,12 @@ class ProxyFinder(object):
def find_module(self, fullname, path=None):
tail = fullname.rsplit('.', 1)[-1]
try:
- self._cache[fullname] = imp.find_module(tail, path)
+ fd, fn, info = imp.find_module(tail, path)
+ if fullname in self._cache:
+ old_fd = self._cache[fullname][0]
+ if old_fd:
+ old_fd.close()
+ self._cache[fullname] = (fd, fn, info)
except ImportError:
return None
else:
@@ -119,7 +124,11 @@ class ProxyFinder(object):
if fullname in sys.modules:
return sys.modules[fullname]
fd, fn, info = self._cache[fullname]
- return imp.load_module(fullname, fd, fn, info)
+ try:
+ return imp.load_module(fullname, fd, fn, info)
+ finally:
+ if fd:
+ fd.close()
class TestFinder(object):
def __init__(self, *args, **kwargs):
diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py
index a7deeeefae..f4fa75b177 100644
--- a/tests/regressiontests/utils/tests.py
+++ b/tests/regressiontests/utils/tests.py
@@ -3,26 +3,28 @@ 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 .module_loading import DefaultLoader, EggLoader, CustomLoader
-from .termcolors import TermColorTests
+from .functional import FunctionalTestCase
from .html import TestUtilsHtml
from .http import TestUtilsHttp
-from .checksums import TestUtilsChecksums
-from .text import TestUtilsText
+from .ipv6 import TestUtilsIPv6
+from .jslex import JsToCForGettextTest, JsTokensTest
+from .module_loading import CustomLoader, DefaultLoader, EggLoader
+from .regex_helper import NormalizeTests
from .simplelazyobject import TestUtilsSimpleLazyObject
-from .decorators import DecoratorFromMiddlewareTests
-from .functional import FunctionalTestCase
+from .termcolors import TermColorTests
+from .text import TestUtilsText
from .timesince import TimesinceTests
-from .datastructures import (MultiValueDictTests, SortedDictTests,
- DictWrapperTests, ImmutableListTests, MergeDictTests)
-from .tzinfo import TzinfoTests
-from .datetime_safe import DatetimeTests
-from .baseconv import TestBaseConv
-from .jslex import JsTokensTest, JsToCForGettextTest
-from .ipv6 import TestUtilsIPv6
from .timezone import TimezoneTests
-from .crypto import TestUtilsCryptoPBKDF2
-from .archive import TestZip, TestTar, TestGzipTar, TestBzip2Tar
-from .regex_helper import NormalizeTests
+from .tzinfo import TzinfoTests
diff --git a/tests/regressiontests/utils/text.py b/tests/regressiontests/utils/text.py
index dd6de63841..ebf67952f9 100644
--- a/tests/regressiontests/utils/text.py
+++ b/tests/regressiontests/utils/text.py
@@ -113,3 +113,11 @@ class TestUtilsText(SimpleTestCase):
self.assertEqual(text.wrap(long_word, 20), long_word)
self.assertEqual(text.wrap('a %s word' % long_word, 10),
'a\n%s\nword' % long_word)
+
+ def test_slugify(self):
+ items = (
+ ('Hello, World!', 'hello-world'),
+ ('spam & eggs', 'spam-eggs'),
+ )
+ for value, output in items:
+ self.assertEqual(text.slugify(value), output)
diff --git a/tests/regressiontests/views/models.py b/tests/regressiontests/views/models.py
index 54f5c1c1f0..461f98c028 100644
--- a/tests/regressiontests/views/models.py
+++ b/tests/regressiontests/views/models.py
@@ -3,16 +3,19 @@ Regression tests for Django built-in views.
"""
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
def get_absolute_url(self):
return '/views/authors/%s/' % self.id
+@python_2_unicode_compatible
class BaseArticle(models.Model):
"""
An abstract article Model so that we can create article models with and
@@ -25,7 +28,7 @@ class BaseArticle(models.Model):
class Meta:
abstract = True
- def __unicode__(self):
+ def __str__(self):
return self.title
class Article(BaseArticle):
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py
index e8a7d49e79..56383ac196 100644
--- a/tests/regressiontests/views/tests/debug.py
+++ b/tests/regressiontests/views/tests/debug.py
@@ -36,7 +36,7 @@ class DebugViewTests(TestCase):
self.assertEqual(response.status_code, 500)
data = {
- 'file_data.txt': SimpleUploadedFile('file_data.txt', 'haha'),
+ 'file_data.txt': SimpleUploadedFile('file_data.txt', b'haha'),
}
response = self.client.post('/raises/', data)
self.assertContains(response, 'file_data.txt', status_code=500)
diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/regressiontests/views/tests/i18n.py
index cb580267d2..601df6d512 100644
--- a/tests/regressiontests/views/tests/i18n.py
+++ b/tests/regressiontests/views/tests/i18n.py
@@ -7,7 +7,8 @@ from os import path
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
-from django.utils.translation import override, activate, get_language
+from django.utils import six
+from django.utils.translation import override, get_language
from django.utils.text import javascript_quote
from ..urls import locale_dir
@@ -29,20 +30,21 @@ class I18NTests(TestCase):
def test_jsi18n(self):
"""The javascript_catalog can be deployed with language settings"""
- saved_lang = get_language()
for lang_code in ['es', 'fr', 'ru']:
- activate(lang_code)
- catalog = gettext.translation('djangojs', locale_dir, [lang_code])
- trans_txt = catalog.ugettext('this is to be translated')
- response = self.client.get('/views/jsi18n/')
- # in response content must to be a line like that:
- # catalog['this is to be translated'] = 'same_that_trans_txt'
- # javascript_quote is used to be able to check unicode strings
- self.assertContains(response, javascript_quote(trans_txt), 1)
- if lang_code == 'fr':
- # Message with context (msgctxt)
- self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
- activate(saved_lang)
+ with override(lang_code):
+ catalog = gettext.translation('djangojs', locale_dir, [lang_code])
+ if six.PY3:
+ trans_txt = catalog.gettext('this is to be translated')
+ else:
+ trans_txt = catalog.ugettext('this is to be translated')
+ response = self.client.get('/views/jsi18n/')
+ # in response content must to be a line like that:
+ # catalog['this is to be translated'] = 'same_that_trans_txt'
+ # javascript_quote is used to be able to check unicode strings
+ self.assertContains(response, javascript_quote(trans_txt), 1)
+ if lang_code == 'fr':
+ # Message with context (msgctxt)
+ self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
class JsI18NTests(TestCase):
diff --git a/tests/regressiontests/views/tests/shortcuts.py b/tests/regressiontests/views/tests/shortcuts.py
index a48bb25d0b..62bd82f4a7 100644
--- a/tests/regressiontests/views/tests/shortcuts.py
+++ b/tests/regressiontests/views/tests/shortcuts.py
@@ -12,44 +12,44 @@ class ShortcutTests(TestCase):
def test_render_to_response(self):
response = self.client.get('/shortcuts/render_to_response/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'FOO.BAR..\n')
+ self.assertEqual(response.content, b'FOO.BAR..\n')
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
def test_render_to_response_with_request_context(self):
response = self.client.get('/shortcuts/render_to_response/request_context/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'FOO.BAR../path/to/static/media/\n')
+ 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/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'FOO.BAR..\n')
+ self.assertEqual(response.content, b'FOO.BAR..\n')
self.assertEqual(response['Content-Type'], 'application/x-rendertest')
def test_render(self):
response = self.client.get('/shortcuts/render/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'FOO.BAR../path/to/static/media/\n')
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
self.assertEqual(response.context.current_app, None)
def test_render_with_base_context(self):
response = self.client.get('/shortcuts/render/base_context/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'FOO.BAR..\n')
+ self.assertEqual(response.content, b'FOO.BAR..\n')
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
def test_render_with_content_type(self):
response = self.client.get('/shortcuts/render/content_type/')
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, 'FOO.BAR../path/to/static/media/\n')
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
self.assertEqual(response['Content-Type'], 'application/x-rendertest')
def test_render_with_status(self):
response = self.client.get('/shortcuts/render/status/')
self.assertEqual(response.status_code, 403)
- self.assertEqual(response.content, 'FOO.BAR../path/to/static/media/\n')
+ self.assertEqual(response.content, b'FOO.BAR../path/to/static/media/\n')
def test_render_with_current_app(self):
response = self.client.get('/shortcuts/render/current_app/')
diff --git a/tests/regressiontests/wsgi/tests.py b/tests/regressiontests/wsgi/tests.py
index a482a5c1eb..5a40bd88e2 100644
--- a/tests/regressiontests/wsgi/tests.py
+++ b/tests/regressiontests/wsgi/tests.py
@@ -40,8 +40,8 @@ class WSGITest(TestCase):
response_data["headers"],
[('Content-Type', 'text/html; charset=utf-8')])
self.assertEqual(
- six.text_type(response),
- "Content-Type: text/html; charset=utf-8\n\nHello World!")
+ bytes(response),
+ b"Content-Type: text/html; charset=utf-8\r\n\r\nHello World!")
class GetInternalWSGIApplicationTest(unittest.TestCase):